diff --git a/.idea/libraries/guice.xml b/.idea/libraries/guice.xml deleted file mode 100644 index b62d5bb76e4..00000000000 --- a/.idea/libraries/guice.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules/elasticsearch.iml b/.idea/modules/elasticsearch.iml index 69b50a8bef9..f50f981bca5 100644 --- a/.idea/modules/elasticsearch.iml +++ b/.idea/modules/elasticsearch.iml @@ -17,7 +17,6 @@ - diff --git a/modules/elasticsearch/build.gradle b/modules/elasticsearch/build.gradle index 281c404f940..85c8c4626ec 100644 --- a/modules/elasticsearch/build.gradle +++ b/modules/elasticsearch/build.gradle @@ -41,11 +41,6 @@ dependencies { compile 'org.codehaus.jackson:jackson-core-asl:1.5.1' compile 'org.codehaus.jackson:jackson-mapper-asl:1.5.1' - compile 'aopalliance:aopalliance:1.0' - compile 'com.google.inject:guice:2.0' - compile 'com.google.inject.extensions:guice-assisted-inject:2.0' - compile 'com.google.inject.extensions:guice-multibindings:2.0' - compile 'org.apache.lucene:lucene-core:3.0.1' compile 'org.apache.lucene:lucene-analyzers:3.0.1' compile 'org.apache.lucene:lucene-queries:3.0.1' diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/TransportActionModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/TransportActionModule.java index 4d2cfe02f54..b3bbadbc885 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/TransportActionModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/TransportActionModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.action; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfo; import org.elasticsearch.action.admin.cluster.node.shutdown.TransportNodesShutdown; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java index d795da151c2..158eb614bf6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.health; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfo.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfo.java index e1f704f34e9..e13ecd43f4b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfo.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfo.java @@ -20,7 +20,7 @@ package org.elasticsearch.action.admin.cluster.node.info; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.nodes.NodeOperationRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/shutdown/TransportNodesShutdown.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/shutdown/TransportNodesShutdown.java index 405c21cafc5..b4db694ed5b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/shutdown/TransportNodesShutdown.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/node/shutdown/TransportNodesShutdown.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.node.shutdown; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/broadcast/TransportBroadcastPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/broadcast/TransportBroadcastPingAction.java index 3bc59d96a15..16d164768a4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/broadcast/TransportBroadcastPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/broadcast/TransportBroadcastPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.ping.broadcast; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportIndexReplicationPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportIndexReplicationPingAction.java index bdbc58d13d7..2731b6b5ee8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportIndexReplicationPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportIndexReplicationPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.ping.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.support.replication.TransportIndexReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.routing.GroupShardsIterator; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportReplicationPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportReplicationPingAction.java index 8d6aa8f3571..13070a47656 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportReplicationPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportReplicationPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.ping.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.replication.TransportIndicesReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportShardReplicationPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportShardReplicationPingAction.java index 06d0d65e3b2..85de31c21d1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportShardReplicationPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/replication/TransportShardReplicationPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.ping.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.support.replication.TransportShardReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/single/TransportSinglePingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/single/TransportSinglePingAction.java index 82105471ba6..e80d0c6dfd9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/single/TransportSinglePingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/ping/single/TransportSinglePingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.ping.single; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.single.TransportSingleOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/state/TransportClusterStateAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/state/TransportClusterStateAction.java index 4089e36411a..1cbdc21f3b8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/state/TransportClusterStateAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/cluster/state/TransportClusterStateAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.cluster.state; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/alias/TransportIndicesAliasesAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/alias/TransportIndicesAliasesAction.java index d1bae1be1dd..181533deca2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/alias/TransportIndicesAliasesAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/alias/TransportIndicesAliasesAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.alias; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/cache/clear/TransportClearIndicesCacheAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/cache/clear/TransportClearIndicesCacheAction.java index d34d8db03fd..4a00de6f18b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/cache/clear/TransportClearIndicesCacheAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/cache/clear/TransportClearIndicesCacheAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.cache.clear; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java index a98191c37b9..404e77c11f2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.create; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java index a40b342d8c6..1bf1bca40d0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/delete/TransportDeleteIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.delete; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/flush/TransportFlushAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/flush/TransportFlushAction.java index 4db8ec07856..b7ee1a2cc3c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/flush/TransportFlushAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/flush/TransportFlushAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.flush; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportGatewaySnapshotAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportGatewaySnapshotAction.java index 39b0191e2e4..81faa5fb097 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportGatewaySnapshotAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportGatewaySnapshotAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.gateway.snapshot; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.replication.TransportIndicesReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportIndexGatewaySnapshotAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportIndexGatewaySnapshotAction.java index 426021b054a..e7fac7b89db 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportIndexGatewaySnapshotAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportIndexGatewaySnapshotAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.gateway.snapshot; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.support.replication.TransportIndexReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.routing.GroupShardsIterator; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportShardGatewaySnapshotAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportShardGatewaySnapshotAction.java index 23bb22b90ff..ec7d279f2ce 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportShardGatewaySnapshotAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/gateway/snapshot/TransportShardGatewaySnapshotAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.gateway.snapshot; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.support.replication.TransportShardReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/TransportPutMappingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/TransportPutMappingAction.java index 68e26608dee..e5e7a27d47b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/TransportPutMappingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/mapping/put/TransportPutMappingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.mapping.put; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.master.TransportMasterNodeOperationAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/optimize/TransportOptimizeAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/optimize/TransportOptimizeAction.java index bce3f65344b..bb2ae9579fe 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/optimize/TransportOptimizeAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/optimize/TransportOptimizeAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.optimize; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportRefreshAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportRefreshAction.java index 5315d86a90c..0cc49b5bc78 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportRefreshAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportRefreshAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.refresh; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/status/TransportIndicesStatusAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/status/TransportIndicesStatusAction.java index c5b4261897f..463922f2ade 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/status/TransportIndicesStatusAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/admin/indices/status/TransportIndicesStatusAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.admin.indices.status; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/count/TransportCountAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/count/TransportCountAction.java index c240b3d9681..a894c93b3d7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/count/TransportCountAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/count/TransportCountAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.count; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/TransportDeleteAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/TransportDeleteAction.java index b588c3bdcfa..52b62296f50 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/TransportDeleteAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/TransportDeleteAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.delete; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java index 4fc122ecd0c..3609317ce54 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportDeleteByQueryAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.deletebyquery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.support.replication.TransportIndicesReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportIndexDeleteByQueryAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportIndexDeleteByQueryAction.java index af5c1ec9672..f9b1b4e81cf 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportIndexDeleteByQueryAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportIndexDeleteByQueryAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.deletebyquery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.support.replication.TransportIndexReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.routing.GroupShardsIterator; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportShardDeleteByQueryAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportShardDeleteByQueryAction.java index f12845cdc25..fad172bfcc3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportShardDeleteByQueryAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/deletebyquery/TransportShardDeleteByQueryAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.deletebyquery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.action.support.replication.TransportShardReplicationOperationAction; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/TransportGetAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/TransportGetAction.java index e8e8c584da8..21d07070885 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/TransportGetAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/TransportGetAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.get; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.Fieldable; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/TransportIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/TransportIndexAction.java index 1205aee6a12..554a09a5ff7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/TransportIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/TransportIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/mlt/TransportMoreLikeThisAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/mlt/TransportMoreLikeThisAction.java index ca99671c2ee..b1e6cbfa68c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/mlt/TransportMoreLikeThisAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/mlt/TransportMoreLikeThisAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.mlt; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.Term; import org.elasticsearch.ElasticSearchException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java index 28ee357fc3b..7087c9ab758 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.search.type.TransportSearchDfsQueryAndFetchAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchScrollAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchScrollAction.java index c86cdd0b0a7..3c2f3eb2224 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchScrollAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/TransportSearchScrollAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.TransportActions; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryAndFetchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryAndFetchAction.java index 4f46b1e1c38..00fb6759ea6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryAndFetchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryAndFetchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search.type; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.*; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryThenFetchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryThenFetchAction.java index b2823339581..600fbb3c1c2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryThenFetchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchDfsQueryThenFetchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search.type; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.*; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryAndFetchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryAndFetchAction.java index 3c4682b68a5..50a6844c9c9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryAndFetchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryAndFetchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search.type; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryThenFetchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryThenFetchAction.java index ef82fa0cc43..ad86f3e555b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryThenFetchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchQueryThenFetchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search.type; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.*; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryAndFetchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryAndFetchAction.java index 5ccde73ec9f..4444b7c11cb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryAndFetchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryAndFetchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search.type; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.*; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryThenFetchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryThenFetchAction.java index a2f1a00eb04..015a2877f00 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryThenFetchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/search/type/TransportSearchScrollQueryThenFetchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.search.type; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.*; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/nodes/TransportNodesOperationAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/nodes/TransportNodesOperationAction.java index 1406bc171da..1ed14fb87b0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/nodes/TransportNodesOperationAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/nodes/TransportNodesOperationAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.support.nodes; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.FailedNodeException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndexReplicationOperationAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndexReplicationOperationAction.java index 7efb50007ee..0eb6373f00c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndexReplicationOperationAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndexReplicationOperationAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.support.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndicesReplicationOperationAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndicesReplicationOperationAction.java index 1e70074823c..770a1d1c1e5 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndicesReplicationOperationAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/replication/TransportIndicesReplicationOperationAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.action.support.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.support.BaseAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java index c99f1ec2a83..1f22a6d067b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/terms/TransportTermsAction.java @@ -20,7 +20,7 @@ package org.elasticsearch.action.terms; import org.elasticsearch.util.gcommon.collect.Maps; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermDocs; import org.apache.lucene.index.TermEnum; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index 71d793e84fb..562001aaf52 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -19,8 +19,8 @@ package org.elasticsearch.bootstrap; -import com.google.inject.CreationException; -import com.google.inject.spi.Message; +import org.elasticsearch.util.guice.inject.CreationException; +import org.elasticsearch.util.guice.inject.spi.Message; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.Version; import org.elasticsearch.env.Environment; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeAdminClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeAdminClient.java index aafe927fdb4..31dc80040ac 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeAdminClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeAdminClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.node; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.ClusterAdminClient; import org.elasticsearch.client.IndicesAdminClient; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java index f364197438a..af5321de6a6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.node; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.count.CountRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClientModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClientModule.java index 1a6a0d188ac..4c123c67229 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClientModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClientModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.node; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.Client; import org.elasticsearch.client.ClusterAdminClient; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClusterAdminClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClusterAdminClient.java index c25bbf2f332..7d45d205a15 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClusterAdminClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClusterAdminClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.node; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeIndicesAdminClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeIndicesAdminClient.java index 8bdb1c451e4..e1065b1497b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeIndicesAdminClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeIndicesAdminClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.node; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/ClientTransportModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/ClientTransportModule.java index 668840bb056..a21922bb7a0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/ClientTransportModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/ClientTransportModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.client.transport.support.InternalTransportAdminClient; import org.elasticsearch.client.transport.support.InternalTransportClient; import org.elasticsearch.client.transport.support.InternalTransportClusterAdminClient; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java index f24ff5df396..6dd5e96cf8e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java @@ -20,9 +20,9 @@ package org.elasticsearch.client.transport; import org.elasticsearch.util.gcommon.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Guice; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterModule.java index 8c2dea59f31..b128aa34103 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.service.InternalClusterService; import org.elasticsearch.discovery.DiscoveryModule; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterService.java index 891dc2bdf2b..f368d80c681 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientClusterService.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.discovery.DiscoveryService; import org.elasticsearch.util.component.AbstractComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientNodesService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientNodesService.java index a8d0c488bb5..235accf854f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientNodesService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClientNodesService.java @@ -20,7 +20,7 @@ package org.elasticsearch.client.transport; import org.elasticsearch.util.gcommon.collect.ImmutableList; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/ClientTransportActionModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/ClientTransportActionModule.java index 113a2232f4e..995943a20cf 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/ClientTransportActionModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/ClientTransportActionModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.client.transport.action.admin.cluster.health.ClientTransportClusterHealthAction; import org.elasticsearch.client.transport.action.admin.cluster.node.info.ClientTransportNodesInfoAction; import org.elasticsearch.client.transport.action.admin.cluster.node.shutdown.ClientTransportNodesShutdownAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/health/ClientTransportClusterHealthAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/health/ClientTransportClusterHealthAction.java index c46aef3ec1d..e889f8ac10f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/health/ClientTransportClusterHealthAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/health/ClientTransportClusterHealthAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.health; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/info/ClientTransportNodesInfoAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/info/ClientTransportNodesInfoAction.java index 7cd260207bf..d1d5d8b5322 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/info/ClientTransportNodesInfoAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/info/ClientTransportNodesInfoAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.node.info; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/shutdown/ClientTransportNodesShutdownAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/shutdown/ClientTransportNodesShutdownAction.java index e9bd92b2a84..56f3dcd724d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/shutdown/ClientTransportNodesShutdownAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/node/shutdown/ClientTransportNodesShutdownAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.node.shutdown; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.node.shutdown.NodesShutdownRequest; import org.elasticsearch.action.admin.cluster.node.shutdown.NodesShutdownResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/broadcast/ClientTransportBroadcastPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/broadcast/ClientTransportBroadcastPingAction.java index 38c97828a3d..0472137a47c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/broadcast/ClientTransportBroadcastPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/broadcast/ClientTransportBroadcastPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.ping.broadcast; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.ping.broadcast.BroadcastPingRequest; import org.elasticsearch.action.admin.cluster.ping.broadcast.BroadcastPingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/replication/ClientTransportReplicationPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/replication/ClientTransportReplicationPingAction.java index 4471b374df5..aa3a22985fb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/replication/ClientTransportReplicationPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/replication/ClientTransportReplicationPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.ping.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.ping.replication.ReplicationPingRequest; import org.elasticsearch.action.admin.cluster.ping.replication.ReplicationPingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/single/ClientTransportSinglePingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/single/ClientTransportSinglePingAction.java index 1b32a207eb5..4198292fd62 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/single/ClientTransportSinglePingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/ping/single/ClientTransportSinglePingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.ping.single; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.ping.single.SinglePingRequest; import org.elasticsearch.action.admin.cluster.ping.single.SinglePingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/state/ClientTransportClusterStateAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/state/ClientTransportClusterStateAction.java index df9f21a0ded..25bda661445 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/state/ClientTransportClusterStateAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/cluster/state/ClientTransportClusterStateAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.cluster.state; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/alias/ClientTransportIndicesAliasesAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/alias/ClientTransportIndicesAliasesAction.java index f9f329f3cac..48eaa69d536 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/alias/ClientTransportIndicesAliasesAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/alias/ClientTransportIndicesAliasesAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.alias; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/cache/clear/ClientTransportClearIndicesCacheAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/cache/clear/ClientTransportClearIndicesCacheAction.java index b2b8adbf829..71eee420bdf 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/cache/clear/ClientTransportClearIndicesCacheAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/cache/clear/ClientTransportClearIndicesCacheAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.cache.clear; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheRequest; import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/create/ClientTransportCreateIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/create/ClientTransportCreateIndexAction.java index 91d1ab71605..20eb899c869 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/create/ClientTransportCreateIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/create/ClientTransportCreateIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.create; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/delete/ClientTransportDeleteIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/delete/ClientTransportDeleteIndexAction.java index a8512c7ce84..dc75118697f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/delete/ClientTransportDeleteIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/delete/ClientTransportDeleteIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.delete; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/flush/ClientTransportFlushAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/flush/ClientTransportFlushAction.java index eafe41d0730..2182d3eae37 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/flush/ClientTransportFlushAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/flush/ClientTransportFlushAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.flush; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.flush.FlushRequest; import org.elasticsearch.action.admin.indices.flush.FlushResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/gateway/snapshot/ClientTransportGatewaySnapshotAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/gateway/snapshot/ClientTransportGatewaySnapshotAction.java index f865d3d36a8..b8781e4d026 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/gateway/snapshot/ClientTransportGatewaySnapshotAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/gateway/snapshot/ClientTransportGatewaySnapshotAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.gateway.snapshot; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.gateway.snapshot.GatewaySnapshotRequest; import org.elasticsearch.action.admin.indices.gateway.snapshot.GatewaySnapshotResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/mapping/create/ClientTransportPutMappingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/mapping/create/ClientTransportPutMappingAction.java index 9df3ad4e33e..32823e93774 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/mapping/create/ClientTransportPutMappingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/mapping/create/ClientTransportPutMappingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.mapping.create; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/optimize/ClientTransportOptimizeAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/optimize/ClientTransportOptimizeAction.java index 6abc8cc9149..23b38e585ff 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/optimize/ClientTransportOptimizeAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/optimize/ClientTransportOptimizeAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.optimize; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.optimize.OptimizeRequest; import org.elasticsearch.action.admin.indices.optimize.OptimizeResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/refresh/ClientTransportRefreshAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/refresh/ClientTransportRefreshAction.java index d0b45a99a86..e750db606dc 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/refresh/ClientTransportRefreshAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/refresh/ClientTransportRefreshAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.refresh; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/status/ClientTransportIndicesStatusAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/status/ClientTransportIndicesStatusAction.java index e0e350c16b8..2b9a40946a0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/status/ClientTransportIndicesStatusAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/admin/indices/status/ClientTransportIndicesStatusAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.admin.indices.status; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.admin.indices.status.IndicesStatusRequest; import org.elasticsearch.action.admin.indices.status.IndicesStatusResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/count/ClientTransportCountAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/count/ClientTransportCountAction.java index cb3681ee17c..dd8f43707e2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/count/ClientTransportCountAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/count/ClientTransportCountAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.count; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.count.CountRequest; import org.elasticsearch.action.count.CountResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/delete/ClientTransportDeleteAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/delete/ClientTransportDeleteAction.java index 13183ea5053..53b813688a6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/delete/ClientTransportDeleteAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/delete/ClientTransportDeleteAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.delete; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/deletebyquery/ClientTransportDeleteByQueryAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/deletebyquery/ClientTransportDeleteByQueryAction.java index 457b70812ff..fc15b614f28 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/deletebyquery/ClientTransportDeleteByQueryAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/deletebyquery/ClientTransportDeleteByQueryAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.deletebyquery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.deletebyquery.DeleteByQueryRequest; import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/get/ClientTransportGetAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/get/ClientTransportGetAction.java index c5bd8863d37..3a7db7b174c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/get/ClientTransportGetAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/get/ClientTransportGetAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.get; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/index/ClientTransportIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/index/ClientTransportIndexAction.java index 2b94dc8d2f8..c79142d4ef9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/index/ClientTransportIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/index/ClientTransportIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/mlt/ClientTransportMoreLikeThisAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/mlt/ClientTransportMoreLikeThisAction.java index 3b7d4b6e5a3..14a785baf43 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/mlt/ClientTransportMoreLikeThisAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/mlt/ClientTransportMoreLikeThisAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.mlt; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.mlt.MoreLikeThisRequest; import org.elasticsearch.action.search.SearchResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchAction.java index f0822efcc3f..5aec226db0a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.search; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchScrollAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchScrollAction.java index 398b492fa62..0997817c3d5 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchScrollAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/search/ClientTransportSearchScrollAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.search; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/terms/ClientTransportTermsAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/terms/ClientTransportTermsAction.java index 4e463af6fc4..24a88bfdf85 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/terms/ClientTransportTermsAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/action/terms/ClientTransportTermsAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.action.terms; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.TransportActions; import org.elasticsearch.action.terms.TermsRequest; import org.elasticsearch.action.terms.TermsResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportAdminClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportAdminClient.java index d9a31812d95..e0a9b7a08fd 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportAdminClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportAdminClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.support; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.client.AdminClient; import org.elasticsearch.client.ClusterAdminClient; import org.elasticsearch.client.IndicesAdminClient; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java index a036ad2d467..e0cdc0b2ab8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.support; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java index 7fa4dbcb783..2bf33c1678f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClusterAdminClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.support; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportIndicesAdminClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportIndicesAdminClient.java index 170f711c73a..2eba52897be 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportIndicesAdminClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportIndicesAdminClient.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.transport.support; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterModule.java index 25b7d6aa9da..f6b88713274 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.cluster.action.index.MappingUpdatedAction; import org.elasticsearch.cluster.action.index.NodeIndexCreatedAction; import org.elasticsearch.cluster.action.index.NodeIndexDeletedAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterNameModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterNameModule.java index 711de7bbf10..aad67fe9d02 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterNameModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/ClusterNameModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.util.settings.Settings; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/MappingUpdatedAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/MappingUpdatedAction.java index 571f206ae2b..166ecbdcb60 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/MappingUpdatedAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/MappingUpdatedAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexCreatedAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexCreatedAction.java index e9affa656f1..c8c4118baf7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexCreatedAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexCreatedAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.node.DiscoveryNodes; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexDeletedAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexDeletedAction.java index 8df5b788f31..5e75784aded 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexDeletedAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeIndexDeletedAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.node.DiscoveryNodes; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeMappingCreatedAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeMappingCreatedAction.java index 8cef1f8bfbf..52bf739fc1c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeMappingCreatedAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/index/NodeMappingCreatedAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.node.DiscoveryNodes; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/shard/ShardStateAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/shard/ShardStateAction.java index 003899f504e..b668a6a7f89 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/shard/ShardStateAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/action/shard/ShardStateAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.action.shard; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/metadata/MetaDataService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/metadata/MetaDataService.java index 9dfe092bbaa..e011d85923b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/metadata/MetaDataService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/metadata/MetaDataService.java @@ -20,7 +20,7 @@ package org.elasticsearch.cluster.metadata; import org.elasticsearch.util.gcommon.collect.Maps; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/routing/RoutingService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/routing/RoutingService.java index de5e2577790..59e6b96eac3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/routing/RoutingService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/routing/RoutingService.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.routing; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.*; import org.elasticsearch.cluster.routing.strategy.ShardsRoutingStrategy; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/service/InternalClusterService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/service/InternalClusterService.java index 3efbc68f92f..dc0d6980d6e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/service/InternalClusterService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/cluster/service/InternalClusterService.java @@ -19,7 +19,7 @@ package org.elasticsearch.cluster.service; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.*; import org.elasticsearch.cluster.node.DiscoveryNode; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryModule.java index 237bb59b8fa..531408779dc 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.discovery; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.discovery.local.LocalDiscoveryModule; import org.elasticsearch.discovery.zen.ZenDiscoveryModule; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryService.java index ba91720818c..8c38774fbb6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/DiscoveryService.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.util.TimeValue; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java index 6e70f7d37d5..7532e386e26 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscovery.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery.local; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.cluster.*; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscoveryModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscoveryModule.java index 14cab3dc2d9..e857ba83cdf 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscoveryModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/local/LocalDiscoveryModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery.local; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.discovery.Discovery; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java index feedb3b4130..9184641f39b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery.zen; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.cluster.*; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscoveryModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscoveryModule.java index 80289d55dc9..c76bdbb8721 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscoveryModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ZenDiscoveryModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery.zen; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.discovery.Discovery; import org.elasticsearch.discovery.zen.ping.ZenPingService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ping/ZenPingService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ping/ZenPingService.java index b58ffc775f3..16d792a68e0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ping/ZenPingService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/discovery/zen/ping/ZenPingService.java @@ -20,7 +20,7 @@ package org.elasticsearch.discovery.zen.ping; import org.elasticsearch.util.gcommon.collect.ImmutableList; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.cluster.ClusterName; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/env/EnvironmentModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/env/EnvironmentModule.java index 96e29950f1e..fe6f9c5ad1b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/env/EnvironmentModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/env/EnvironmentModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.env; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * @author kimchy (Shay Banon) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/Gateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/Gateway.java index 47517fe16fe..df75017d11c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/Gateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/Gateway.java @@ -19,7 +19,7 @@ package org.elasticsearch.gateway; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.util.component.LifecycleComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayModule.java index ee149e2f393..ab0912d458a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.gateway; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.gateway.none.NoneGatewayModule; import org.elasticsearch.util.guice.ModulesFactory; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayService.java index 2576c2437ef..4324b509b6f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/GatewayService.java @@ -19,7 +19,7 @@ package org.elasticsearch.gateway; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.*; import org.elasticsearch.cluster.metadata.IndexMetaData; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGateway.java index f011cc6e280..1aa9197144c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGateway.java @@ -19,8 +19,8 @@ package org.elasticsearch.gateway.fs; -import com.google.inject.Inject; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Module; import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonParser; import org.elasticsearch.ElasticSearchException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGatewayModule.java index 2cd475ad778..6170fc5b40c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/fs/FsGatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.gateway.fs; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.gateway.Gateway; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGateway.java index 74e98f072bb..3f12f4987fd 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGateway.java @@ -19,8 +19,8 @@ package org.elasticsearch.gateway.none; -import com.google.inject.Inject; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.gateway.Gateway; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGatewayModule.java index 3b54e17b82b..f23b9232e10 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/gateway/none/NoneGatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.gateway.none; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.gateway.Gateway; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServer.java b/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServer.java index 7df75d41c35..00a5c6bb49c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServer.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServer.java @@ -19,7 +19,7 @@ package org.elasticsearch.http; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfo; import org.elasticsearch.rest.JsonThrowableRestResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServerModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServerModule.java index 998c5e117ef..73651063a69 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServerModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/http/HttpServerModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.http; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.Classes; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransport.java b/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransport.java index a88ac9ffc8a..64b6f11d33b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransport.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransport.java @@ -19,7 +19,7 @@ package org.elasticsearch.http.netty; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.http.*; import org.elasticsearch.transport.BindTransportException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransportModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransportModule.java index a36e06d4b6d..2b1128626a7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransportModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/http/netty/NettyHttpServerTransportModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.http.netty; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.http.HttpServerTransport; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexModule.java index b33872090f0..198e2725591 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.service.InternalIndexService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexNameModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexNameModule.java index 2349cba175f..8eacfa021fb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexNameModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexNameModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * @author kimchy (Shay Banon) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexServiceManagement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexServiceManagement.java index 42af1ef0a19..7e15cd48a89 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexServiceManagement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/IndexServiceManagement.java @@ -19,7 +19,7 @@ package org.elasticsearch.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.jmx.JmxService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeId.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeId.java index 5b6b7b3da36..8603828ba5f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeId.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeId.java @@ -19,7 +19,7 @@ package org.elasticsearch.index; -import com.google.inject.BindingAnnotation; +import org.elasticsearch.util.guice.inject.BindingAnnotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeIdModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeIdModule.java index b876b583c9c..f82870ff47b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeIdModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/LocalNodeIdModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * @author kimchy (Shay Banon) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ASCIIFoldingTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ASCIIFoldingTokenFilterFactory.java index 6fab0676171..1f06b31111f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ASCIIFoldingTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ASCIIFoldingTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.ASCIIFoldingFilter; import org.apache.lucene.analysis.TokenStream; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisModule.java index 061c0cd0a3c..fff240e1332 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisModule.java @@ -19,10 +19,10 @@ package org.elasticsearch.index.analysis; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; -import com.google.inject.assistedinject.FactoryProvider; -import com.google.inject.multibindings.MapBinder; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Scopes; +import org.elasticsearch.util.guice.inject.assistedinject.FactoryProvider; +import org.elasticsearch.util.guice.inject.multibindings.MapBinder; import org.elasticsearch.util.settings.Settings; import java.util.Map; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java index 39c8ae12097..1411895b7e3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalysisService.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.analysis.Analyzer; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalyzerProvider.java index fe1c9962e6d..519aac8e64a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/AnalyzerProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Provider; +import org.elasticsearch.util.guice.inject.Provider; import org.apache.lucene.analysis.Analyzer; import org.elasticsearch.index.IndexComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicAnalyzerProvider.java index 5904c70a910..23151a8ea44 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.ar.ArabicAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicStemTokenFilterFactory.java index 9b368da5e3d..764529c091b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ArabicStemTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ar.ArabicStemFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianAnalyzerProvider.java index 2e9042b653d..d59075ae7ee 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.br.BrazilianAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianStemTokenFilterFactory.java index 8de735a25f5..ac2a0b5c94f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/BrazilianStemTokenFilterFactory.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.br.BrazilianStemFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ChineseAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ChineseAnalyzerProvider.java index fe43dd06e8e..8bd6a3cf0e7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ChineseAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ChineseAnalyzerProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.cn.ChineseAnalyzer; import org.elasticsearch.index.Index; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CjkAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CjkAnalyzerProvider.java index 52553cc09e5..34ebe367c78 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CjkAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CjkAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.cjk.CJKAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java index 3e22e2f549d..3611e05f1c1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CustomAnalyzerProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.elasticsearch.index.Index; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.util.settings.ImmutableSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CzechAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CzechAnalyzerProvider.java index f1489f6c9f7..6d10a5aa167 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CzechAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/CzechAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.cz.CzechAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchAnalyzerProvider.java index 1e9ccf43129..99b9190279a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.nl.DutchAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchStemTokenFilterFactory.java index cbc155fbb83..09c886dbbf5 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/DutchStemTokenFilterFactory.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.nl.DutchStemFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java index c4e2d468ac9..da41caa1068 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ngram.EdgeNGramTokenFilter; import org.apache.lucene.analysis.ngram.EdgeNGramTokenizer; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenizerFactory.java index 470d68c34f6..e81db3e0acf 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/EdgeNGramTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.ngram.EdgeNGramTokenizer; import org.apache.lucene.analysis.ngram.NGramTokenizer; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchAnalyzerProvider.java index b7ea6d865bd..070c4090f4e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.fr.FrenchAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchStemTokenFilterFactory.java index 7eef5ed88ac..054d3ce3c2c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/FrenchStemTokenFilterFactory.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.fr.FrenchStemFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanAnalyzerProvider.java index d4d4d27101b..6f496af11c8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.de.GermanAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanStemTokenFilterFactory.java index 59fa291f801..ad2e0d3e372 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GermanStemTokenFilterFactory.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.de.GermanStemFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GreekAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GreekAnalyzerProvider.java index 33abc9f6705..36e4d14eec1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GreekAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/GreekAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.el.GreekAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordAnalyzerProvider.java index fb39efbc09c..6ce7a53df1f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordAnalyzerProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.KeywordAnalyzer; import org.elasticsearch.index.Index; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordTokenizerFactory.java index b1465eade9d..7dbacfa3c60 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/KeywordTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.KeywordTokenizer; import org.apache.lucene.analysis.Tokenizer; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java index 9f35e00d872..aa3acc317a8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LengthTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.LengthFilter; import org.apache.lucene.analysis.TokenStream; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LetterTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LetterTokenizerFactory.java index b50092cf27c..c013c018a59 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LetterTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LetterTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.LetterTokenizer; import org.apache.lucene.analysis.Tokenizer; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java index 2cf5bc2fc2b..e43fd04563a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.LowerCaseFilter; import org.apache.lucene.analysis.TokenStream; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenizerFactory.java index 80665c088a0..79c7523a06c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/LowerCaseTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.LowerCaseTokenizer; import org.apache.lucene.analysis.Tokenizer; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java index 555a07e54a6..fcad67258ea 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ngram.NGramTokenFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenizerFactory.java index 1731af8ae04..434564bf2a0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/NGramTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.ngram.NGramTokenizer; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PersianAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PersianAnalyzerProvider.java index c5297e05281..2e0e14874cd 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PersianAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PersianAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.fa.PersianAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PorterStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PorterStemTokenFilterFactory.java index ae37763f214..5b19c1a36d8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PorterStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/PorterStemTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.PorterStemFilter; import org.apache.lucene.analysis.TokenStream; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianAnalyzerProvider.java index f4ce8acd4e0..84b786fb05f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.ru.RussianAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianStemTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianStemTokenFilterFactory.java index c566cce0871..a7214950357 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianStemTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/RussianStemTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.ru.RussianStemFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ShingleTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ShingleTokenFilterFactory.java index 657cbf0f047..4b2f7b55a20 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ShingleTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ShingleTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.shingle.ShingleFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/SimpleAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/SimpleAnalyzerProvider.java index 3d579a1f6a6..41ea33982e6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/SimpleAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/SimpleAnalyzerProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.SimpleAnalyzer; import org.elasticsearch.index.Index; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardAnalyzerProvider.java index 9d4153ba3fe..e837655bc95 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.StopAnalyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.util.Version; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenFilterFactory.java index cef4797eb2f..a500e2a2859 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenFilterFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.standard.StandardFilter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenizerFactory.java index 2f042a203af..facdea4cdce 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StandardTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.analysis.standard.StandardTokenizer; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopAnalyzerProvider.java index 22a611314c4..7477a2526cd 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopAnalyzerProvider.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.StopAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopTokenFilterFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopTokenFilterFactory.java index d67ddaaaae7..396eca973dc 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopTokenFilterFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/StopTokenFilterFactory.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.analysis; import org.elasticsearch.util.gcommon.collect.ImmutableSet; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.StopAnalyzer; import org.apache.lucene.analysis.StopFilter; import org.apache.lucene.analysis.TokenStream; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ThaiAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ThaiAnalyzerProvider.java index 44ee99bed23..9791da7927a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ThaiAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/ThaiAnalyzerProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.th.ThaiAnalyzer; import org.apache.lucene.util.Version; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceAnalyzerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceAnalyzerProvider.java index d46e9473d72..e4d194b7881 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceAnalyzerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceAnalyzerProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.WhitespaceAnalyzer; import org.elasticsearch.index.Index; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceTokenizerFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceTokenizerFactory.java index 7872247a43b..7a0790196a4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceTokenizerFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/analysis/WhitespaceTokenizerFactory.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.analysis.Tokenizer; import org.apache.lucene.analysis.WhitespaceTokenizer; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCache.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCache.java index b5a5aefe80a..4b0b79991da 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCache.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCache.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.cache; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; import org.elasticsearch.index.cache.filter.FilterCache; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java index 6244ac71a0e..c80828218cb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/IndexCacheModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.cache; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.cache.filter.FilterCacheModule; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/FilterCacheModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/FilterCacheModule.java index 9f64c612261..4e277ba9cff 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/FilterCacheModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/FilterCacheModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.cache.filter; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Scopes; import org.elasticsearch.index.cache.filter.soft.SoftFilterCache; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/none/NoneFilterCache.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/none/NoneFilterCache.java index 60a8db2ede2..72c710c37a1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/none/NoneFilterCache.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/none/NoneFilterCache.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.cache.filter.none; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Filter; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/soft/SoftFilterCache.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/soft/SoftFilterCache.java index e26bab14212..33e8060189a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/soft/SoftFilterCache.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/soft/SoftFilterCache.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.cache.filter.soft; import org.elasticsearch.util.gcommon.collect.MapMaker; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.Filter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/weak/WeakFilterCache.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/weak/WeakFilterCache.java index 397f803deec..b29b9f45e5e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/weak/WeakFilterCache.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/cache/filter/weak/WeakFilterCache.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.cache.filter.weak; import org.elasticsearch.util.gcommon.collect.MapMaker; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.Filter; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/DeletionPolicyModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/DeletionPolicyModule.java index 863dd908af9..fc88ad4a414 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/DeletionPolicyModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/DeletionPolicyModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.deletionpolicy; -import com.google.inject.AbstractModule; -import com.google.inject.name.Names; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.name.Names; import org.apache.lucene.index.IndexDeletionPolicy; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepLastNDeletionPolicy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepLastNDeletionPolicy.java index eebe36f4dce..9a2a5814178 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepLastNDeletionPolicy.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepLastNDeletionPolicy.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.deletionpolicy; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexCommit; import org.apache.lucene.index.IndexDeletionPolicy; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepOnlyLastDeletionPolicy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepOnlyLastDeletionPolicy.java index 1fdc1c3dbb8..ac5a57a19ca 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepOnlyLastDeletionPolicy.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/KeepOnlyLastDeletionPolicy.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.deletionpolicy; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexCommit; import org.apache.lucene.index.IndexDeletionPolicy; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/SnapshotDeletionPolicy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/SnapshotDeletionPolicy.java index 0e84ced6fd3..990b4f795c0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/SnapshotDeletionPolicy.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/deletionpolicy/SnapshotDeletionPolicy.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.deletionpolicy; -import com.google.inject.Inject; -import com.google.inject.name.Named; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.name.Named; import org.apache.lucene.index.IndexCommit; import org.apache.lucene.index.IndexDeletionPolicy; import org.elasticsearch.index.shard.AbstractIndexShardComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/EngineModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/EngineModule.java index 233b289e0a9..72d4e53ee33 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/EngineModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/EngineModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.engine; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.engine.robin.RobinEngineModule; import org.elasticsearch.util.guice.ModulesFactory; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngine.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngine.java index 79b8582c53a..16f3709b4e9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngine.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngine.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.engine.robin; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.LogMergePolicy; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngineModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngineModule.java index 661309959c2..d6817d1d1f6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngineModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/engine/robin/RobinEngineModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.engine.robin; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.engine.Engine; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexGatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexGatewayModule.java index ea011996f77..8b70c6e0457 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexGatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexGatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.gateway.Gateway; import org.elasticsearch.util.guice.ModulesFactory; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayModule.java index dd69c5d06c1..b467624496d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * @author kimchy (Shay Banon) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayService.java index 1c8268ecce3..b76eaa20bb3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/IndexShardGatewayService.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.index.deletionpolicy.SnapshotIndexCommit; import org.elasticsearch.index.engine.Engine; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGateway.java index dc83bfa262d..44b09b71d88 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGateway.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway.fs; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.env.Environment; import org.elasticsearch.gateway.Gateway; import org.elasticsearch.gateway.fs.FsGateway; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGatewayModule.java index e62c061a96d..fbc7bb3258d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexGatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway.fs; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.gateway.IndexGateway; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexShardGateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexShardGateway.java index 1404e72d389..533a88d706d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexShardGateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/fs/FsIndexShardGateway.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway.fs; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexReader; import org.apache.lucene.store.IndexInput; import org.elasticsearch.index.deletionpolicy.SnapshotIndexCommit; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGateway.java index d540b77a571..f226f8d36d1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGateway.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway.none; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; import org.elasticsearch.index.gateway.IndexGateway; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGatewayModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGatewayModule.java index ceacef35dee..b9689d2f92b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGatewayModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexGatewayModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway.none; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.gateway.IndexGateway; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexShardGateway.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexShardGateway.java index 1995c0826b5..0536f784547 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexShardGateway.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/gateway/none/NoneIndexShardGateway.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.gateway.none; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.gateway.IndexShardGateway; import org.elasticsearch.index.gateway.IndexShardGatewayRecoveryException; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperService.java index ddb0954a930..f87d6273dfa 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperService.java @@ -21,7 +21,7 @@ package org.elasticsearch.index.mapper; import org.elasticsearch.util.gcommon.collect.ImmutableMap; import org.elasticsearch.util.gcommon.collect.UnmodifiableIterator; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import org.elasticsearch.env.Environment; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperServiceModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperServiceModule.java index 577e8e9e344..be66a33c27e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperServiceModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/MapperServiceModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.mapper; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * @author kimchy (Shay Banon) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/BalancedSegmentMergePolicyProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/BalancedSegmentMergePolicyProvider.java index 106b0f4e105..2d9ff31d266 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/BalancedSegmentMergePolicyProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/BalancedSegmentMergePolicyProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.policy; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.LogByteSizeMergePolicy; import org.apache.lucene.index.LogMergePolicy; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogByteSizeMergePolicyProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogByteSizeMergePolicyProvider.java index 528d04f40d5..042c746f15a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogByteSizeMergePolicyProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogByteSizeMergePolicyProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.policy; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.LogByteSizeMergePolicy; import org.elasticsearch.index.shard.AbstractIndexShardComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogDocMergePolicyProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogDocMergePolicyProvider.java index c03fa42f2ab..4fac3f84a6b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogDocMergePolicyProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/LogDocMergePolicyProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.policy; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.LogDocMergePolicy; import org.elasticsearch.index.shard.AbstractIndexShardComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergeFactor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergeFactor.java index 6791395b505..fef7923b486 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergeFactor.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergeFactor.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.policy; -import com.google.inject.BindingAnnotation; +import org.elasticsearch.util.guice.inject.BindingAnnotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergePolicyModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergePolicyModule.java index 6a1378cadd9..bdc359f9a03 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergePolicyModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/policy/MergePolicyModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.policy; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.apache.lucene.index.LogMergePolicy; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/ConcurrentMergeSchedulerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/ConcurrentMergeSchedulerProvider.java index 371979db190..9f5b3008c67 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/ConcurrentMergeSchedulerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/ConcurrentMergeSchedulerProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.scheduler; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.ConcurrentMergeScheduler; import org.apache.lucene.index.MergeScheduler; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/MergeSchedulerModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/MergeSchedulerModule.java index 97993708b3c..10862b8e6e9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/MergeSchedulerModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/MergeSchedulerModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.scheduler; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.util.settings.Settings; import static org.elasticsearch.index.merge.scheduler.MergeSchedulerModule.MergeSchedulerSettings.*; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/SerialMergeSchedulerProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/SerialMergeSchedulerProvider.java index 787696ac5e0..8119d82ebce 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/SerialMergeSchedulerProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/merge/scheduler/SerialMergeSchedulerProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.merge.scheduler; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.MergeScheduler; import org.apache.lucene.index.SerialMergeScheduler; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserModule.java index e932d386851..2f06f1a8b4e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserModule.java @@ -19,10 +19,10 @@ package org.elasticsearch.index.query; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; -import com.google.inject.assistedinject.FactoryProvider; -import com.google.inject.multibindings.MapBinder; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Scopes; +import org.elasticsearch.util.guice.inject.assistedinject.FactoryProvider; +import org.elasticsearch.util.guice.inject.multibindings.MapBinder; import org.elasticsearch.index.query.json.*; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java index f5cdc1b85b0..ea5184ff717 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/IndexQueryParserService.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.query; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; import org.elasticsearch.index.analysis.AnalysisService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonFilterParser.java index 0039156435e..53994c1bd1f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonFilterParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.*; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonQueryParser.java index 6dfe4e31bd1..2780035ecc0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/BoolJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/ConstantScoreQueryJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/ConstantScoreQueryJsonQueryParser.java index 176d0b8a2c3..20813873f3c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/ConstantScoreQueryJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/ConstantScoreQueryJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/DisMaxJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/DisMaxJsonQueryParser.java index bf62b70d6d1..2c102131a35 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/DisMaxJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/DisMaxJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.DisjunctionMaxQuery; import org.apache.lucene.search.Query; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FieldJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FieldJsonQueryParser.java index 3fcfc25a95b..eabcd9bab98 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FieldJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FieldJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FilteredJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FilteredJsonQueryParser.java index 3491e751eab..e470a09a3af 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FilteredJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/FilteredJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Filter; import org.apache.lucene.search.FilteredQuery; import org.apache.lucene.search.Query; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/JsonIndexQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/JsonIndexQueryParser.java index 6f7363bbae5..e9f82dfa9c0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/JsonIndexQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/JsonIndexQueryParser.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.search.Query; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/MatchAllJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/MatchAllJsonQueryParser.java index 90521bdbc07..1b57adb3ab8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/MatchAllJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/MatchAllJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonFilterParser.java index ed8136488c5..712dcc16903 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonFilterParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.Filter; import org.apache.lucene.search.PrefixFilter; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonQueryParser.java index 8b3dda3f131..08e56cafcdd 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/PrefixJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.PrefixQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryJsonFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryJsonFilterParser.java index ed8479f0225..6a6ebef65fb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryJsonFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryJsonFilterParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; import org.apache.lucene.search.QueryWrapperFilter; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryStringJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryStringJsonQueryParser.java index 0bda1d5dbad..e62f5d010f6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryStringJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/QueryStringJsonQueryParser.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.query.json; import org.elasticsearch.util.gcommon.collect.Lists; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonFilterParser.java index 38faed96d9b..37041fccac3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonFilterParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Filter; import org.apache.lucene.search.TermRangeFilter; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonQueryParser.java index ef9db424c10..8b113c3f746 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/RangeJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermRangeQuery; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanFirstJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanFirstJsonQueryParser.java index e21dcb64537..fa7b8ad09f6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanFirstJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanFirstJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Query; import org.apache.lucene.search.spans.SpanFirstQuery; import org.apache.lucene.search.spans.SpanQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNearJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNearJsonQueryParser.java index b6780119d05..ef7fd9747cd 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNearJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNearJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Query; import org.apache.lucene.search.spans.SpanNearQuery; import org.apache.lucene.search.spans.SpanQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNotJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNotJsonQueryParser.java index f7a357da75f..124a6fea147 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNotJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanNotJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Query; import org.apache.lucene.search.spans.SpanNotQuery; import org.apache.lucene.search.spans.SpanQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanOrJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanOrJsonQueryParser.java index b9d53e429f3..5fd54d6b0c2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanOrJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanOrJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Query; import org.apache.lucene.search.spans.SpanOrQuery; import org.apache.lucene.search.spans.SpanQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanTermJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanTermJsonQueryParser.java index 14a746630ff..44ed9949e1d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanTermJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/SpanTermJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.Query; import org.apache.lucene.search.spans.SpanTermQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonFilterParser.java index ff3c0e58a16..8d212fd66da 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonFilterParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.Filter; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonQueryParser.java index c677284ece4..5a586c3c6dc 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermsJsonFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermsJsonFilterParser.java index 4dc52eb4908..cdc84076965 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermsJsonFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/TermsJsonFilterParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.Filter; import org.apache.lucene.search.PublicTermsFilter; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/WildcardJsonQueryParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/WildcardJsonQueryParser.java index 0f3ca3368cd..23bf15c86e6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/WildcardJsonQueryParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/json/WildcardJsonQueryParser.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.query.json; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.index.Term; import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.Query; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/OperationRoutingModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/OperationRoutingModule.java index aef944d88fa..a3031806e84 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/OperationRoutingModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/OperationRoutingModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.routing; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.routing.hash.HashFunction; import org.elasticsearch.index.routing.hash.djb.DjbHashFunction; import org.elasticsearch.index.routing.plain.PlainOperationRoutingModule; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRouting.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRouting.java index 3498c115bf2..09c8cca2aad 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRouting.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRouting.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.routing.plain; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.routing.GroupShardsIterator; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRoutingModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRoutingModule.java index 382497717e7..e7a26254093 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRoutingModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/routing/plain/PlainOperationRoutingModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.routing.plain; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.routing.OperationRouting; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/IndexService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/IndexService.java index f166f9d0f40..1d671b726c8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/IndexService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/IndexService.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.service; -import com.google.inject.Injector; +import org.elasticsearch.util.guice.inject.Injector; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.index.IndexComponent; import org.elasticsearch.index.IndexShardMissingException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java index 4336c395743..8f8848bc82f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/service/InternalIndexService.java @@ -21,8 +21,8 @@ package org.elasticsearch.index.service; import org.elasticsearch.util.gcommon.collect.ImmutableMap; import org.elasticsearch.util.gcommon.collect.UnmodifiableIterator; -import com.google.inject.Inject; -import com.google.inject.Injector; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Injector; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettings.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettings.java index b217f74c301..091a5e70d56 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettings.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettings.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.settings; -import com.google.inject.BindingAnnotation; +import org.elasticsearch.util.guice.inject.BindingAnnotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettingsModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettingsModule.java index 36e02a6e9e4..58a92e93efb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettingsModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/settings/IndexSettingsModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.settings; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.util.settings.Settings; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardManagement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardManagement.java index a99ed3d4d03..492f0f6ef53 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardManagement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardManagement.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.shard; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.service.IndexShard; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardModule.java index 9cdcf8ba770..aca581c3ca1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/IndexShardModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.shard; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.shard.recovery.RecoveryAction; import org.elasticsearch.index.shard.service.IndexShard; import org.elasticsearch.index.shard.service.InternalIndexShard; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/recovery/RecoveryAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/recovery/RecoveryAction.java index 825b738321e..918d217543b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/recovery/RecoveryAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/recovery/RecoveryAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.shard.recovery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; import org.elasticsearch.ElasticSearchException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java index d73233c3da4..96cf1a46584 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/shard/service/InternalIndexShard.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.shard.service; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.document.Document; import org.apache.lucene.index.Term; import org.apache.lucene.search.*; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/DefaultSimilarityProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/DefaultSimilarityProvider.java index bfcd0743a39..886ea1d7340 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/DefaultSimilarityProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/DefaultSimilarityProvider.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.similarity; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.search.DefaultSimilarity; import org.elasticsearch.index.Index; import org.elasticsearch.index.settings.IndexSettings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityModule.java index d26be93f7b4..e966c999f61 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityModule.java @@ -19,10 +19,10 @@ package org.elasticsearch.index.similarity; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; -import com.google.inject.assistedinject.FactoryProvider; -import com.google.inject.multibindings.MapBinder; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Scopes; +import org.elasticsearch.util.guice.inject.assistedinject.FactoryProvider; +import org.elasticsearch.util.guice.inject.multibindings.MapBinder; import org.elasticsearch.util.settings.Settings; import java.util.Map; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityProvider.java index 1fa7c03a8b5..deaf66ec4be 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityProvider.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityProvider.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.similarity; -import com.google.inject.Provider; +import org.elasticsearch.util.guice.inject.Provider; import org.apache.lucene.search.Similarity; import org.elasticsearch.index.IndexComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityService.java index 8cee04fe922..6044e82788b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/similarity/SimilarityService.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.similarity; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.Similarity; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreManagement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreManagement.java index cb3e38924f6..41ccb3f2c6a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreManagement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreManagement.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.shard.AbstractIndexShardComponent; import org.elasticsearch.jmx.MBean; import org.elasticsearch.jmx.ManagedAttribute; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreModule.java index 8495ed2129d..47e332940d8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/StoreModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.store; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.index.store.fs.MmapFsStoreModule; import org.elasticsearch.index.store.fs.NioFsStoreModule; import org.elasticsearch.index.store.fs.SimpleFsStoreModule; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStore.java index cb79a108eb3..52ca927592b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStore.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStore.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.fs; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.MMapDirectory; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStoreModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStoreModule.java index 6da56a7fa8e..ce178694bc6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStoreModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/MmapFsStoreModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.fs; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.store.Store; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStore.java index b1ca7001da0..240ca13daf4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStore.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStore.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.fs; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.NIOFSDirectory; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStoreModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStoreModule.java index 1773cdaa292..c945d1d1576 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStoreModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/NioFsStoreModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.fs; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.store.Store; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStore.java index 64fbf637b42..9fb9b212b62 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStore.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStore.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.fs; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.SimpleFSDirectory; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStoreModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStoreModule.java index ec734b3243d..5093585c85f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStoreModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/fs/SimpleFsStoreModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.fs; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.store.Store; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/ByteBufferStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/ByteBufferStore.java index bed3df62a76..0705ea541d2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/ByteBufferStore.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/ByteBufferStore.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.memory; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.store.support.AbstractStore; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/HeapStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/HeapStore.java index 0ce6ba4b5ca..bb781454938 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/HeapStore.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/HeapStore.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.memory; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.store.support.AbstractStore; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/MemoryStoreModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/MemoryStoreModule.java index 3ba9f7695f1..087f69ac119 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/MemoryStoreModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/memory/MemoryStoreModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.memory; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.index.store.Store; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStore.java index ed0730ade57..116ed758ace 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStore.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStore.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.ram; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.store.RAMDirectory; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.ShardId; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStoreModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStoreModule.java index 78207a163dc..6180cd5ba89 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStoreModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/store/ram/RamStoreModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.store.ram; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.index.store.Store; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/TranslogModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/TranslogModule.java index 9618bb90b48..6f99105e4db 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/TranslogModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/TranslogModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.translog; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Scopes; import org.elasticsearch.index.translog.memory.MemoryTranslog; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/memory/MemoryTranslog.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/memory/MemoryTranslog.java index 37942e5e331..f75e8541b51 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/memory/MemoryTranslog.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/translog/memory/MemoryTranslog.java @@ -19,7 +19,7 @@ package org.elasticsearch.index.translog.memory; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.settings.IndexSettings; import org.elasticsearch.index.shard.AbstractIndexShardComponent; import org.elasticsearch.index.shard.ShardId; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesMemoryCleaner.java b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesMemoryCleaner.java index 1a2da23854a..106ee4c373e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesMemoryCleaner.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesMemoryCleaner.java @@ -19,7 +19,7 @@ package org.elasticsearch.indices; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.FlushNotAllowedEngineException; import org.elasticsearch.index.service.IndexService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesModule.java index 83ef8773924..0ee5caf561b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/IndicesModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.indices; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.indices.cluster.IndicesClusterStateService; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesLifecycle.java b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesLifecycle.java index 66dd27aabbd..f5adc9b1ee0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesLifecycle.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesLifecycle.java @@ -19,7 +19,7 @@ package org.elasticsearch.indices; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.Index; import org.elasticsearch.index.service.IndexService; import org.elasticsearch.index.shard.ShardId; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java index c3c5148e2c0..bed8ce100d8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/InternalIndicesService.java @@ -21,8 +21,8 @@ package org.elasticsearch.indices; import org.elasticsearch.util.gcommon.collect.ImmutableMap; import org.elasticsearch.util.gcommon.collect.UnmodifiableIterator; -import com.google.inject.Inject; -import com.google.inject.Injector; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Injector; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.routing.GroupShardsIterator; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java index b283d28c735..27f196d229f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/indices/cluster/IndicesClusterStateService.java @@ -20,7 +20,7 @@ package org.elasticsearch.indices.cluster; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/JmxModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/JmxModule.java index 2ccc498eccd..86bbce74af3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/JmxModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/JmxModule.java @@ -19,12 +19,12 @@ package org.elasticsearch.jmx; -import com.google.inject.AbstractModule; -import com.google.inject.TypeLiteral; -import com.google.inject.matcher.Matchers; -import com.google.inject.spi.InjectionListener; -import com.google.inject.spi.TypeEncounter; -import com.google.inject.spi.TypeListener; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.matcher.Matchers; +import org.elasticsearch.util.guice.inject.spi.InjectionListener; +import org.elasticsearch.util.guice.inject.spi.TypeEncounter; +import org.elasticsearch.util.guice.inject.spi.TypeListener; import org.elasticsearch.jmx.action.GetJmxServiceUrlAction; import org.elasticsearch.util.logging.Loggers; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/action/GetJmxServiceUrlAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/action/GetJmxServiceUrlAction.java index 62f5e172f86..bd1c6566068 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/action/GetJmxServiceUrlAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/jmx/action/GetJmxServiceUrlAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.jmx.action; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.node.DiscoveryNode; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorModule.java index 0f7c7fe0fa9..6d427c4993a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorModule.java @@ -19,10 +19,10 @@ package org.elasticsearch.monitor; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; -import com.google.inject.assistedinject.FactoryProvider; -import com.google.inject.multibindings.MapBinder; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Scopes; +import org.elasticsearch.util.guice.inject.assistedinject.FactoryProvider; +import org.elasticsearch.util.guice.inject.multibindings.MapBinder; import org.elasticsearch.monitor.dump.DumpContributorFactory; import org.elasticsearch.monitor.dump.DumpMonitorService; import org.elasticsearch.monitor.dump.cluster.ClusterDumpContributor; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorService.java index 328b6316501..d742672f2c1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/MonitorService.java @@ -19,7 +19,7 @@ package org.elasticsearch.monitor; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.monitor.jvm.JvmMonitorService; import org.elasticsearch.monitor.memory.MemoryMonitorService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/DumpMonitorService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/DumpMonitorService.java index 0331b2c5577..69ce1f198e2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/DumpMonitorService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/DumpMonitorService.java @@ -19,7 +19,7 @@ package org.elasticsearch.monitor.dump; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.env.Environment; import org.elasticsearch.monitor.dump.heap.HeapDumpContributor; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/cluster/ClusterDumpContributor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/cluster/ClusterDumpContributor.java index 1335ada63ee..7944f8caa5a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/cluster/ClusterDumpContributor.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/cluster/ClusterDumpContributor.java @@ -19,8 +19,8 @@ package org.elasticsearch.monitor.dump.cluster; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.node.DiscoveryNodes; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/heap/HeapDumpContributor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/heap/HeapDumpContributor.java index 6f2989ae8d5..56c2d91bdca 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/heap/HeapDumpContributor.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/heap/HeapDumpContributor.java @@ -19,8 +19,8 @@ package org.elasticsearch.monitor.dump.heap; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.elasticsearch.monitor.dump.Dump; import org.elasticsearch.monitor.dump.DumpContributionFailedException; import org.elasticsearch.monitor.dump.DumpContributor; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/summary/SummaryDumpContributor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/summary/SummaryDumpContributor.java index 8d7020fbf1b..fdae3874ec9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/summary/SummaryDumpContributor.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/summary/SummaryDumpContributor.java @@ -19,8 +19,8 @@ package org.elasticsearch.monitor.dump.summary; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.elasticsearch.monitor.dump.Dump; import org.elasticsearch.monitor.dump.DumpContributionFailedException; import org.elasticsearch.monitor.dump.DumpContributor; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/thread/ThreadDumpContributor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/thread/ThreadDumpContributor.java index f69482b19f5..f37c4949ed9 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/thread/ThreadDumpContributor.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/dump/thread/ThreadDumpContributor.java @@ -19,8 +19,8 @@ package org.elasticsearch.monitor.dump.thread; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.elasticsearch.monitor.dump.Dump; import org.elasticsearch.monitor.dump.DumpContributionFailedException; import org.elasticsearch.monitor.dump.DumpContributor; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/jvm/JvmMonitorService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/jvm/JvmMonitorService.java index 869c1cefd46..23148ef6096 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/jvm/JvmMonitorService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/jvm/JvmMonitorService.java @@ -20,7 +20,7 @@ package org.elasticsearch.monitor.jvm; import org.elasticsearch.util.gcommon.collect.ImmutableSet; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.monitor.dump.DumpGenerator; import org.elasticsearch.monitor.dump.DumpMonitorService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/MemoryMonitorService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/MemoryMonitorService.java index 3fec8e7f93d..615daa8982f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/MemoryMonitorService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/MemoryMonitorService.java @@ -19,7 +19,7 @@ package org.elasticsearch.monitor.memory; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.util.component.AbstractLifecycleComponent; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/alpha/AlphaMemoryMonitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/alpha/AlphaMemoryMonitor.java index 38eba9aeb22..e0757b63bab 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/alpha/AlphaMemoryMonitor.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/monitor/memory/alpha/AlphaMemoryMonitor.java @@ -19,7 +19,7 @@ package org.elasticsearch.monitor.memory.alpha; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.indices.IndicesMemoryCleaner; import org.elasticsearch.monitor.memory.MemoryMonitor; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/InternalNode.java b/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/InternalNode.java index 929e16e112e..d4f0eb9ca36 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/InternalNode.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/InternalNode.java @@ -19,9 +19,9 @@ package org.elasticsearch.node.internal; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Guice; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.Version; import org.elasticsearch.action.TransportActionModule; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/NodeModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/NodeModule.java index fba383ffa85..ea95f950a20 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/NodeModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/node/internal/NodeModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.node.internal; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.node.Node; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/AbstractPlugin.java b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/AbstractPlugin.java index 13ffdd1fa75..1dbcf979357 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/AbstractPlugin.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/AbstractPlugin.java @@ -20,7 +20,7 @@ package org.elasticsearch.plugins; import org.elasticsearch.util.gcommon.collect.ImmutableList; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.component.CloseableIndexComponent; import org.elasticsearch.util.component.LifecycleComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/IndicesPluginsModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/IndicesPluginsModule.java index de488be8d8d..f3edb386ab8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/IndicesPluginsModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/IndicesPluginsModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.plugins; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.settings.Settings; import java.util.Collection; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/Plugin.java b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/Plugin.java index 1ef73a6206f..2d371ee876d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/Plugin.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/Plugin.java @@ -19,7 +19,7 @@ package org.elasticsearch.plugins; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.component.CloseableIndexComponent; import org.elasticsearch.util.component.LifecycleComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsModule.java index ccca3f22dc5..bb9b7d23fcf 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.plugins; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.settings.Settings; import java.util.Collection; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsService.java index d3062b97df8..496233dd3d2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/PluginsService.java @@ -22,8 +22,8 @@ package org.elasticsearch.plugins; import org.elasticsearch.util.gcommon.collect.ImmutableMap; import org.elasticsearch.util.gcommon.collect.Lists; import org.elasticsearch.util.gcommon.collect.Maps; -import com.google.inject.Inject; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.env.Environment; import org.elasticsearch.util.component.AbstractComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/ShardsPluginsModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/ShardsPluginsModule.java index cdb4c3643dc..091df06a4c0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/ShardsPluginsModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/plugins/ShardsPluginsModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.plugins; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.settings.Settings; import java.util.Collection; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestController.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestController.java index 2cec7363731..5fdf958ef4f 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestController.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestController.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.util.component.AbstractLifecycleComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestModule.java index 06cf03c1ecf..73148c50942 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/RestModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.rest.action.RestActionModule; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/RestActionModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/RestActionModule.java index 8a4fffc1071..aadde28f221 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/RestActionModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/RestActionModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.rest.action.admin.cluster.health.RestClusterHealthAction; import org.elasticsearch.rest.action.admin.cluster.node.info.RestNodesInfoAction; import org.elasticsearch.rest.action.admin.cluster.node.shutdown.RestNodesShutdownAction; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/health/RestClusterHealthAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/health/RestClusterHealthAction.java index eb7a5e3f68b..063e2bbe8c3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/health/RestClusterHealthAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/health/RestClusterHealthAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.health; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.health.*; import org.elasticsearch.client.Client; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java index 9af8c720e6c..01eb2b9fb43 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.node.info; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/shutdown/RestNodesShutdownAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/shutdown/RestNodesShutdownAction.java index 69550bda5d2..b5909bcd5c8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/shutdown/RestNodesShutdownAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/shutdown/RestNodesShutdownAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.node.shutdown; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.node.shutdown.NodesShutdownRequest; import org.elasticsearch.action.admin.cluster.node.shutdown.NodesShutdownResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/broadcast/RestBroadcastPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/broadcast/RestBroadcastPingAction.java index 4951e0e60cc..da53903ddc7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/broadcast/RestBroadcastPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/broadcast/RestBroadcastPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.ping.broadcast; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.ping.broadcast.BroadcastPingRequest; import org.elasticsearch.action.admin.cluster.ping.broadcast.BroadcastPingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/replication/RestReplicationPingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/replication/RestReplicationPingAction.java index 64d758074f9..b4805b12ab6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/replication/RestReplicationPingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/replication/RestReplicationPingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.ping.replication; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.ping.replication.IndexReplicationPingResponse; import org.elasticsearch.action.admin.cluster.ping.replication.ReplicationPingRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/single/RestSinglePingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/single/RestSinglePingAction.java index 4f4df6d8cd5..096110a7d22 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/single/RestSinglePingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/ping/single/RestSinglePingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.ping.single; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.ping.single.SinglePingRequest; import org.elasticsearch.action.admin.cluster.ping.single.SinglePingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/state/RestClusterStateAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/state/RestClusterStateAction.java index 90c38b5d676..abac37370f4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/state/RestClusterStateAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/cluster/state/RestClusterStateAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster.state; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/alias/RestIndicesAliasesAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/alias/RestIndicesAliasesAction.java index fc330f75235..5a91b12a7f7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/alias/RestIndicesAliasesAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/alias/RestIndicesAliasesAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.alias; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.elasticsearch.ElasticSearchIllegalArgumentException; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/cache/clear/RestClearIndicesCacheAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/cache/clear/RestClearIndicesCacheAction.java index 7a7018074e8..da72b8233f0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/cache/clear/RestClearIndicesCacheAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/cache/clear/RestClearIndicesCacheAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.cache.clear; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheRequest; import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/create/RestCreateIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/create/RestCreateIndexAction.java index 39df4d64563..0cbf26f864c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/create/RestCreateIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/create/RestCreateIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.create; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/delete/RestDeleteIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/delete/RestDeleteIndexAction.java index 79c4403a3fb..b6f4ba85743 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/delete/RestDeleteIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/delete/RestDeleteIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.delete; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/flush/RestFlushAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/flush/RestFlushAction.java index 9fa5bc5efd1..651033ca2d4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/flush/RestFlushAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/flush/RestFlushAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.flush; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.flush.FlushRequest; import org.elasticsearch.action.admin.indices.flush.FlushResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/gateway/snapshot/RestGatewaySnapshotAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/gateway/snapshot/RestGatewaySnapshotAction.java index 92835e5391f..99dd59bd324 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/gateway/snapshot/RestGatewaySnapshotAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/gateway/snapshot/RestGatewaySnapshotAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.gateway.snapshot; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.gateway.snapshot.GatewaySnapshotRequest; import org.elasticsearch.action.admin.indices.gateway.snapshot.GatewaySnapshotResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/mapping/put/RestPutMappingAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/mapping/put/RestPutMappingAction.java index 227013f4b93..938fe9b914a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/mapping/put/RestPutMappingAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/mapping/put/RestPutMappingAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.mapping.put; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/optimize/RestOptimizeAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/optimize/RestOptimizeAction.java index 420ce7eef00..6a16aa5e683 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/optimize/RestOptimizeAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/optimize/RestOptimizeAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.optimize; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.optimize.OptimizeRequest; import org.elasticsearch.action.admin.indices.optimize.OptimizeResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/refresh/RestRefreshAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/refresh/RestRefreshAction.java index 24696131336..8114eaf9098 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/refresh/RestRefreshAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/refresh/RestRefreshAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.refresh; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/status/RestIndicesStatusAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/status/RestIndicesStatusAction.java index d92a08f3a5e..2d3348af55c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/status/RestIndicesStatusAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/admin/indices/status/RestIndicesStatusAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.admin.indices.status; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.status.*; import org.elasticsearch.action.support.broadcast.BroadcastOperationThreading; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java index 554f6fd8b79..7fdc2bbd3db 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/count/RestCountAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.count; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.count.CountRequest; import org.elasticsearch.action.count.CountResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/delete/RestDeleteAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/delete/RestDeleteAction.java index fbcfa43b478..d0be783be5c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/delete/RestDeleteAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/delete/RestDeleteAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.delete; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java index 855e183c6fa..955a7523149 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/deletebyquery/RestDeleteByQueryAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.deletebyquery; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.deletebyquery.DeleteByQueryRequest; import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/get/RestGetAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/get/RestGetAction.java index f3a1e24ba8b..675f182d325 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/get/RestGetAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/get/RestGetAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.get; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.get.GetField; import org.elasticsearch.action.get.GetRequest; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/index/RestIndexAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/index/RestIndexAction.java index c1fae2fac4d..1a0099d5846 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/index/RestIndexAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/index/RestIndexAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.index; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java index 8cb8fbf2653..f5fe4593e64 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/main/RestMainAction.java @@ -20,7 +20,7 @@ package org.elasticsearch.rest.action.main; import org.elasticsearch.util.gcommon.collect.Iterators; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.node.ArrayNode; import org.elasticsearch.Version; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/mlt/RestMoreLikeThisAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/mlt/RestMoreLikeThisAction.java index e240194a3f4..12ded4aaa45 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/mlt/RestMoreLikeThisAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/mlt/RestMoreLikeThisAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.mlt; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.mlt.MoreLikeThisRequest; import org.elasticsearch.action.search.SearchResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java index 2d3ece36a3e..c6b3f897165 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.search; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchOperationThreading; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java index b17e4459223..796b4f6959d 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.search; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchOperationThreading; import org.elasticsearch.action.search.SearchResponse; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/terms/RestTermsAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/terms/RestTermsAction.java index 06e1ba5dbfe..ad19a2305bb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/terms/RestTermsAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/rest/action/terms/RestTermsAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.rest.action.terms; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.broadcast.BroadcastOperationThreading; import org.elasticsearch.action.terms.FieldTermsFreq; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java index 8e47606253d..7d8eb0651f3 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.search; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.search.action.SearchServiceTransportAction; import org.elasticsearch.search.controller.SearchPhaseController; import org.elasticsearch.search.dfs.DfsPhase; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java index cd1db3c9f1b..ee76ee29fa4 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/SearchService.java @@ -20,7 +20,7 @@ package org.elasticsearch.search; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.TopDocs; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonParser; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/action/SearchServiceTransportAction.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/action/SearchServiceTransportAction.java index 466b642c35a..f16a42f1953 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/action/SearchServiceTransportAction.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/action/SearchServiceTransportAction.java @@ -19,7 +19,7 @@ package org.elasticsearch.search.action; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.search.SearchService; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index 4aa90967fb4..82ca4e1ffb8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -20,7 +20,7 @@ package org.elasticsearch.search.fetch; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.document.Document; import org.apache.lucene.document.FieldSelector; import org.apache.lucene.document.Fieldable; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java b/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java index 9898816ffd8..ed138642433 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/search/query/QueryPhase.java @@ -20,7 +20,7 @@ package org.elasticsearch.search.query; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.apache.lucene.search.*; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.search.SearchParseElement; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/ThreadPoolModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/ThreadPoolModule.java index 6c2df427387..23636bf8e16 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/ThreadPoolModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/ThreadPoolModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.threadpool; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.threadpool.cached.CachedThreadPool; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/blocking/BlockingThreadPool.java b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/blocking/BlockingThreadPool.java index a2bb5e2e66d..2c467cf42f0 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/blocking/BlockingThreadPool.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/blocking/BlockingThreadPool.java @@ -19,7 +19,7 @@ package org.elasticsearch.threadpool.blocking; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.threadpool.support.AbstractThreadPool; import org.elasticsearch.util.SizeUnit; import org.elasticsearch.util.SizeValue; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/cached/CachedThreadPool.java b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/cached/CachedThreadPool.java index b0d85970f87..3ac27e0ce9a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/cached/CachedThreadPool.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/cached/CachedThreadPool.java @@ -19,7 +19,7 @@ package org.elasticsearch.threadpool.cached; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.threadpool.support.AbstractThreadPool; import org.elasticsearch.util.TimeValue; import org.elasticsearch.util.concurrent.DynamicExecutors; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/scaling/ScalingThreadPool.java b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/scaling/ScalingThreadPool.java index c8aec5642e3..55a52e21dde 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/scaling/ScalingThreadPool.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/threadpool/scaling/ScalingThreadPool.java @@ -19,7 +19,7 @@ package org.elasticsearch.threadpool.scaling; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.threadpool.support.AbstractThreadPool; import org.elasticsearch.util.TimeValue; import org.elasticsearch.util.concurrent.DynamicExecutors; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerModule.java index a332fe80bfd..c4e0dd4ea44 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.timer; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * @author kimchy (Shay Banon) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerService.java index ac123fffd34..4a3d416fc18 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/timer/TimerService.java @@ -19,7 +19,7 @@ package org.elasticsearch.timer; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.util.TimeValue; import org.elasticsearch.util.component.AbstractComponent; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportModule.java index 962b4b5eb06..54457fc4f51 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.transport; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.transport.local.LocalTransportModule; import org.elasticsearch.util.Classes; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportService.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportService.java index 37cb31a7052..c929b71e22a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportService.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportService.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.threadpool.ThreadPool; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportServiceManagement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportServiceManagement.java index b9ab8c7baaf..18bf5635f3a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportServiceManagement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/TransportServiceManagement.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.jmx.MBean; import org.elasticsearch.jmx.ManagedAttribute; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransport.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransport.java index aa1b81cbc17..190b6b4aa09 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransport.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransport.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport.local; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.threadpool.ThreadPool; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportManagement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportManagement.java index f93fedb7d83..55ddd33c0ed 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportManagement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportManagement.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport.local; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.jmx.MBean; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportModule.java index d3f33c8ca9e..ff07aae0c51 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/local/LocalTransportModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport.local; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.transport.Transport; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransport.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransport.java index b48d5c1a414..496853699fa 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransport.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransport.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport.netty; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.cluster.node.DiscoveryNode; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportManagement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportManagement.java index e60510d0020..2223f1e8509 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportManagement.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportManagement.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport.netty; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.jmx.MBean; import org.elasticsearch.jmx.ManagedAttribute; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportModule.java index f8546ff9c5f..f1796a4d1b5 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/netty/NettyTransportModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.transport.netty; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.transport.Transport; /** diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/Classes.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/Classes.java index a185cc8ca19..30d40d84f7e 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/Classes.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/Classes.java @@ -19,6 +19,8 @@ package org.elasticsearch.util; +import java.lang.reflect.Modifier; + /** * @author kimchy (Shay Banon) */ @@ -80,6 +82,16 @@ public class Classes { return fullPackage; } + public static boolean isInnerClass(Class clazz) { + return !Modifier.isStatic(clazz.getModifiers()) + && clazz.getEnclosingClass() != null; + } + + public static boolean isConcrete(Class clazz) { + int modifiers = clazz.getModifiers(); + return !clazz.isInterface() && !Modifier.isAbstract(modifiers); + } + private Classes() { } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/GwtCompatible.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/GwtCompatible.java new file mode 100644 index 00000000000..3abe0bfbc0d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/GwtCompatible.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on a type indicates that the type may be + * used with the + * Google Web Toolkit (GWT). + * When applied to a method, the return type of the method is GWT compatible. + * It's useful to indicate that an instance created by factory methods has a GWT + * serializable type. In the following example, + * + *
+ * {@literal @}GwtCompatible
+ * class Lists {
+ *   ...
+ *   {@literal @}GwtCompatible(serializable = true)
+ *   static <E> List<E> newArrayList(E... elements) {
+ *     ...
+ *   }
+ * }
+ * 
+ * The return value of {@code Lists.newArrayList(E[])} has GWT + * serializable type. It is also useful in specifying contracts of interface + * methods. In the following example, + * + *
+ * {@literal @}GwtCompatible
+ * interface ListFactory {
+ *   ...
+ *   {@literal @}GwtCompatible(serializable = true)
+ *   <E> List<E> newArrayList(E... elements);
+ * }
+ * 
+ * The {@code newArrayList(E[])} method of all implementations of {@code + * ListFactory} is expected to return a value with a GWT serializable type. + * + *

Note that a {@code GwtCompatible} type may have some {@link + * GwtIncompatible} methods. + * + * @author Charles Fry + * @author Hayward Chan + */ +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.TYPE, ElementType.METHOD }) +// @Documented - uncomment when GWT support is official +@GwtCompatible +public @interface GwtCompatible { + + /** + * When {@code true}, the annotated type or the type of the method return + * value is GWT serializable. + * + * @see + * Documentation about GWT serialization + */ + boolean serializable() default false; + + /** + * When {@code true}, the annotated type is emulated in GWT. The emulated + * source (also known as super-source) is different from the implementation + * used by the JVM. + * + * @see + * Documentation about GWT emulated source + */ + boolean emulated() default false; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/GwtIncompatible.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/GwtIncompatible.java new file mode 100644 index 00000000000..dae25ba6fbf --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/GwtIncompatible.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The presence of this annotation on a method indicates that the method may + * not be used with the + * Google Web Toolkit (GWT), + * even though its type is annotated as {@link GwtCompatible} and accessible in + * GWT. They can cause GWT compilation errors or simply unexpected exceptions + * when used in GWT. + * + *

Note that this annotation should only be applied to methods of types which + * are annotated as {@link GwtCompatible}. + * + * @author Charles Fry + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +// @Documented - uncomment when GWT support is official +@GwtCompatible +public @interface GwtIncompatible { + + /** + * Describes why the annotated element is incompatible with GWT. Since this is + * generally due to a dependence on a type/method which GWT doesn't support, + * it is sufficient to simply reference the unsupported type/method. E.g. + * "Class.isInstance". + */ + String value(); + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/VisibleForTesting.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/VisibleForTesting.java new file mode 100644 index 00000000000..57f03301aa9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/annotations/VisibleForTesting.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.annotations; + +/** + * An annotation that indicates that the visibility of a type or member has + * been relaxed to make the code testable. + * + * @author Johannes Henkel + */ +@GwtCompatible +public @interface VisibleForTesting {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizablePhantomReference.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizablePhantomReference.java new file mode 100644 index 00000000000..d61f98f3efc --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizablePhantomReference.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import java.lang.ref.PhantomReference; + +/** + * Phantom reference with a {@code finalizeReferent()} method which a + * background thread invokes after the garbage collector reclaims the + * referent. This is a simpler alternative to using a {@link + * java.lang.ref.ReferenceQueue}. + * + *

Unlike a normal phantom reference, this reference will be cleared + * automatically. + * + * @author Bob Lee + */ +public abstract class FinalizablePhantomReference + extends PhantomReference implements FinalizableReference { + + /** + * Constructs a new finalizable phantom reference. + * + * @param referent to phantom reference + * @param queue that should finalize the referent + */ + protected FinalizablePhantomReference(T referent, + FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableReference.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableReference.java new file mode 100644 index 00000000000..0377bfb1e4c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableReference.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +/** + * Implemented by references that have code to run after garbage collection of + * their referents. + * + * @see FinalizableReferenceQueue + * @author Bob Lee + */ +public interface FinalizableReference { + + /** + * Invoked on a background thread after the referent has been garbage + * collected unless security restrictions prevented starting a background + * thread, in which case this method is invoked when new references + * are created. + */ + void finalizeReferent(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableReferenceQueue.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableReferenceQueue.java new file mode 100644 index 00000000000..19559ec2b91 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableReferenceQueue.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A reference queue with an associated background thread that dequeues + * references and invokes {@link FinalizableReference#finalizeReferent()} on + * them. + * + *

Keep a strong reference to this object until all of the associated + * referents have been finalized. If this object is garbage collected earlier, + * the backing thread will not invoke {@code finalizeReferent()} on the + * remaining references. + * + * @author Bob Lee + */ +public class FinalizableReferenceQueue { + + /* + * The Finalizer thread keeps a phantom reference to this object. When the + * client (ReferenceMap, for example) no longer has a strong reference to + * this object, the garbage collector will reclaim it and enqueue the + * phantom reference. The enqueued reference will trigger the Finalizer to + * stop. + * + * If this library is loaded in the system class loader, + * FinalizableReferenceQueue can load Finalizer directly with no problems. + * + * If this library is loaded in an application class loader, it's important + * that Finalizer not have a strong reference back to the class loader. + * Otherwise, you could have a graph like this: + * + * Finalizer Thread + * runs instance of -> Finalizer.class + * loaded by -> Application class loader + * which loaded -> ReferenceMap.class + * which has a static -> FinalizableReferenceQueue instance + * + * Even if no other references to classes from the application class loader + * remain, the Finalizer thread keeps an indirect strong reference to the + * queue in ReferenceMap, which keeps the Finalizer running, and as a result, + * the application class loader can never be reclaimed. + * + * This means that dynamically loaded web applications and OSGi bundles can't + * be unloaded. + * + * If the library is loaded in an application class loader, we try to break + * the cycle by loading Finalizer in its own independent class loader: + * + * System class loader + * -> Application class loader + * -> ReferenceMap + * -> FinalizableReferenceQueue + * -> etc. + * -> Decoupled class loader + * -> Finalizer + * + * Now, Finalizer no longer keeps an indirect strong reference to the + * static FinalizableReferenceQueue field in ReferenceMap. The application + * class loader can be reclaimed at which point the Finalizer thread will + * stop and its decoupled class loader can also be reclaimed. + * + * If any of this fails along the way, we fall back to loading Finalizer + * directly in the application class loader. + */ + + private static final Logger logger + = Logger.getLogger(FinalizableReferenceQueue.class.getName()); + + private static final String FINALIZER_CLASS_NAME + = "org.elasticsearch.util.gcommon.base.internal.Finalizer"; + + /** Reference to Finalizer.startFinalizer(). */ + private static final Method startFinalizer; + static { + Class finalizer = loadFinalizer( + new SystemLoader(), new DecoupledLoader(), new DirectLoader()); + startFinalizer = getStartFinalizer(finalizer); + } + + /** + * The actual reference queue that our background thread will poll. + */ + final ReferenceQueue queue; + + /** + * Whether or not the background thread started successfully. + */ + final boolean threadStarted; + + /** + * Constructs a new queue. + */ + @SuppressWarnings("unchecked") + public FinalizableReferenceQueue() { + // We could start the finalizer lazily, but I'd rather it blow up early. + ReferenceQueue queue; + boolean threadStarted = false; + try { + queue = (ReferenceQueue) startFinalizer.invoke(null, + FinalizableReference.class, this); + threadStarted = true; + } catch (IllegalAccessException e) { + // Finalizer.startFinalizer() is public. + throw new AssertionError(e); + } catch (Throwable t) { + logger.log(Level.INFO, "Failed to start reference finalizer thread." + + " Reference cleanup will only occur when new references are" + + " created.", t); + queue = new ReferenceQueue(); + } + + this.queue = queue; + this.threadStarted = threadStarted; + } + + /** + * Repeatedly dequeues references from the queue and invokes + * {@link FinalizableReference#finalizeReferent()} on them until the queue + * is empty. This method is a no-op if the background thread was created + * successfully. + */ + void cleanUp() { + if (threadStarted) { + return; + } + + Reference reference; + while ((reference = queue.poll()) != null) { + /* + * This is for the benefit of phantom references. Weak and soft + * references will have already been cleared by this point. + */ + reference.clear(); + try { + ((FinalizableReference) reference).finalizeReferent(); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + } + } + + /** + * Iterates through the given loaders until it finds one that can load + * Finalizer. + * + * @return Finalizer.class + */ + private static Class loadFinalizer(FinalizerLoader... loaders) { + for (FinalizerLoader loader : loaders) { + Class finalizer = loader.loadFinalizer(); + if (finalizer != null) { + return finalizer; + } + } + + throw new AssertionError(); + } + + /** + * Loads Finalizer.class. + */ + interface FinalizerLoader { + + /** + * Returns Finalizer.class or null if this loader shouldn't or can't load + * it. + * + * @throws SecurityException if we don't have the appropriate privileges + */ + Class loadFinalizer(); + } + + /** + * Tries to load Finalizer from the system class loader. If Finalizer is + * in the system class path, we needn't create a separate loader. + */ + static class SystemLoader implements FinalizerLoader { + public Class loadFinalizer() { + ClassLoader systemLoader; + try { + systemLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException e) { + logger.info("Not allowed to access system class loader."); + return null; + } + if (systemLoader != null) { + try { + return systemLoader.loadClass(FINALIZER_CLASS_NAME); + } catch (ClassNotFoundException e) { + // Ignore. Finalizer is simply in a child class loader. + return null; + } + } else { + return null; + } + } + } + + /** + * Try to load Finalizer in its own class loader. If Finalizer's thread + * had a direct reference to our class loader (which could be that of + * a dynamically loaded web application or OSGi bundle), it would prevent + * our class loader from getting garbage collected. + */ + static class DecoupledLoader implements FinalizerLoader { + + private static final String LOADING_ERROR = "Could not load Finalizer in" + + " its own class loader. Loading Finalizer in the current class loader" + + " instead. As a result, you will not be able to garbage collect this" + + " class loader. To support reclaiming this class loader, either" + + " resolve the underlying issue, or move Google Collections to your" + + " system class path."; + + public Class loadFinalizer() { + try { + /* + * We use URLClassLoader because it's the only concrete class loader + * implementation in the JDK. If we used our own ClassLoader subclass, + * Finalizer would indirectly reference this class loader: + * + * Finalizer.class -> + * CustomClassLoader -> + * CustomClassLoader.class -> + * This class loader + * + * System class loader will (and must) be the parent. + */ + ClassLoader finalizerLoader = newLoader(getBaseUrl()); + return finalizerLoader.loadClass(FINALIZER_CLASS_NAME); + } catch (Exception e) { + logger.log(Level.WARNING, LOADING_ERROR, e); + return null; + } + } + + /** + * Gets URL for base of path containing Finalizer.class. + */ + URL getBaseUrl() throws IOException { + // Find URL pointing to Finalizer.class file. + String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class"; + URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath); + if (finalizerUrl == null) { + throw new FileNotFoundException(finalizerPath); + } + + // Find URL pointing to base of class path. + String urlString = finalizerUrl.toString(); + if (!urlString.endsWith(finalizerPath)) { + throw new IOException("Unsupported path style: " + urlString); + } + urlString = urlString.substring(0, + urlString.length() - finalizerPath.length()); + return new URL(finalizerUrl, urlString); + } + + /** Creates a class loader with the given base URL as its classpath. */ + URLClassLoader newLoader(URL base) { + return new URLClassLoader(new URL[] { base }); + } + } + + /** + * Loads Finalizer directly using the current class loader. We won't be + * able to garbage collect this class loader, but at least the world + * doesn't end. + */ + static class DirectLoader implements FinalizerLoader { + public Class loadFinalizer() { + try { + return Class.forName(FINALIZER_CLASS_NAME); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + } + + /** + * Looks up Finalizer.startFinalizer(). + */ + static Method getStartFinalizer(Class finalizer) { + try { + return finalizer.getMethod("startFinalizer", Class.class, Object.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableSoftReference.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableSoftReference.java new file mode 100644 index 00000000000..d050ab544fa --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableSoftReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import java.lang.ref.SoftReference; + +/** + * Soft reference with a {@code finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author Bob Lee + */ +public abstract class FinalizableSoftReference extends SoftReference + implements FinalizableReference { + + /** + * Constructs a new finalizable soft reference. + * + * @param referent to softly reference + * @param queue that should finalize the referent + */ + protected FinalizableSoftReference(T referent, + FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableWeakReference.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableWeakReference.java new file mode 100644 index 00000000000..30fcd6a737b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/FinalizableWeakReference.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import java.lang.ref.WeakReference; + +/** + * Weak reference with a {@code finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author Bob Lee + */ +public abstract class FinalizableWeakReference extends WeakReference + implements FinalizableReference { + + /** + * Constructs a new finalizable weak reference. + * + * @param referent to weakly reference + * @param queue that should finalize the referent + */ + protected FinalizableWeakReference(T referent, + FinalizableReferenceQueue queue) { + super(referent, queue.queue); + queue.cleanUp(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Function.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Function.java new file mode 100644 index 00000000000..0a7af2fc278 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Function.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import javax.annotation.Nullable; + +/** + * A transformation from one object to another. For example, a + * {@code StringToIntegerFunction} may implement + * Function<String,Integer> and transform integers in + * {@code String} format to {@code Integer} format. + * + *

The transformation on the source object does not necessarily result in + * an object of a different type. For example, a + * {@code FarenheitToCelsiusFunction} may implement + * Function<Float,Float>. + * + *

Implementations which may cause side effects upon evaluation are strongly + * encouraged to state this fact clearly in their API documentation. + * + * @param the type of the function input + * @param the type of the function output + * @author Kevin Bourrillion + * @author Scott Bonneau + */ +@GwtCompatible +public interface Function { + + /** + * Applies the function to an object of type {@code F}, resulting in an object + * of type {@code T}. Note that types {@code F} and {@code T} may or may not + * be the same. + * + * @param from the source object + * @return the resulting object + */ + T apply(@Nullable F from); + + /** + * Indicates whether some other object is equal to this {@code Function}. + * This method can return {@code true} only if the specified object is + * also a {@code Function} and, for every input object {@code o}, it returns + * exactly the same value. Thus, {@code function1.equals(function2)} implies + * that either {@code function1.apply(o)} and {@code function2.apply(o)} are + * both null, or {@code function1.apply(o).equals(function2.apply(o))}. + * + *

Note that it is always safe not to override + * {@link Object#equals}. + */ + boolean equals(@Nullable Object obj); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Functions.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Functions.java new file mode 100644 index 00000000000..baa092b16f2 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Functions.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Useful functions. + * + *

All methods returns serializable functions as long as they're given + * serializable parameters. + * + * @author Mike Bostock + * @author Vlad Patryshev + * @author Jared Levy + */ +@GwtCompatible +public final class Functions { + private Functions() {} + + /** + * Returns a function that calls {@code toString()} on its argument. The + * function does not accept nulls; it will throw a + * {@link NullPointerException} when applied to {@code null}. + */ + public static Function toStringFunction() { + return ToStringFunction.INSTANCE; + } + + // enum singleton pattern + private enum ToStringFunction implements Function { + INSTANCE; + + public String apply(Object o) { + return o.toString(); + } + + @Override public String toString() { + return "toString"; + } + } + + /** + * Returns the identity function. + */ + @SuppressWarnings("unchecked") + public static Function identity() { + return (Function) IdentityFunction.INSTANCE; + } + + // enum singleton pattern + private enum IdentityFunction implements Function { + INSTANCE; + + public Object apply(Object o) { + return o; + } + + @Override public String toString() { + return "identity"; + } + } + + /** + * Returns a function which performs a map lookup. The returned function + * throws an {@link IllegalArgumentException} if given a key that does not + * exist in the map. + */ + public static Function forMap(Map map) { + return new FunctionForMapNoDefault(map); + } + + private static class FunctionForMapNoDefault + implements Function, Serializable { + final Map map; + + FunctionForMapNoDefault(Map map) { + this.map = checkNotNull(map); + } + public V apply(K key) { + V result = map.get(key); + checkArgument(result != null || map.containsKey(key), + "Key '%s' not present in map", key); + return result; + } + @Override public boolean equals(Object o) { + if (o instanceof FunctionForMapNoDefault) { + FunctionForMapNoDefault that = (FunctionForMapNoDefault) o; + return map.equals(that.map); + } + return false; + } + @Override public int hashCode() { + return map.hashCode(); + } + @Override public String toString() { + return "forMap(" + map + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a function which performs a map lookup with a default value. The + * function created by this method returns {@code defaultValue} for all + * inputs that do not belong to the map's key set. + * + * @param map source map that determines the function behavior + * @param defaultValue the value to return for inputs that aren't map keys + * @return function that returns {@code map.get(a)} when {@code a} is a key, + * or {@code defaultValue} otherwise + */ + public static Function forMap( + Map map, @Nullable V defaultValue) { + return new ForMapWithDefault(map, defaultValue); + } + + private static class ForMapWithDefault + implements Function, Serializable { + final Map map; + final V defaultValue; + + ForMapWithDefault(Map map, V defaultValue) { + this.map = checkNotNull(map); + this.defaultValue = defaultValue; + } + public V apply(K key) { + return map.containsKey(key) ? map.get(key) : defaultValue; + } + @Override public boolean equals(Object o) { + if (o instanceof ForMapWithDefault) { + ForMapWithDefault that = (ForMapWithDefault) o; + return map.equals(that.map) + && Objects.equal(defaultValue, that.defaultValue); + } + return false; + } + @Override public int hashCode() { + return Objects.hashCode(map, defaultValue); + } + @Override public String toString() { + return "forMap(" + map + ", defaultValue=" + defaultValue + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns the composition of two functions. For {@code f: A->B} and + * {@code g: B->C}, composition is defined as the function h such that + * {@code h(a) == g(f(a))} for each {@code a}. + * + * @see + * function composition + * + * @param g the second function to apply + * @param f the first function to apply + * @return the composition of {@code f} and {@code g} + */ + public static Function compose( + Function g, Function f) { + return new FunctionComposition(g, f); + } + + private static class FunctionComposition + implements Function, Serializable { + private final Function g; + private final Function f; + + public FunctionComposition(Function g, + Function f) { + this.g = checkNotNull(g); + this.f = checkNotNull(f); + } + public C apply(A a) { + return g.apply(f.apply(a)); + } + @Override public boolean equals(Object obj) { + if (obj instanceof FunctionComposition) { + FunctionComposition that = (FunctionComposition) obj; + return f.equals(that.f) && g.equals(that.g); + } + return false; + } + + @Override public int hashCode() { + return f.hashCode() ^ g.hashCode(); + } + @Override public String toString() { + return g.toString() + "(" + f.toString() + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns the same boolean output as the given + * predicate for all inputs. + */ + public static Function forPredicate(Predicate predicate) { + return new PredicateFunction(predicate); + } + + /** @see Functions#forPredicate */ + private static class PredicateFunction + implements Function, Serializable { + private final Predicate predicate; + + private PredicateFunction(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + + public Boolean apply(T t) { + return predicate.apply(t); + } + @Override public boolean equals(Object obj) { + if (obj instanceof PredicateFunction) { + PredicateFunction that = (PredicateFunction) obj; + return predicate.equals(that.predicate); + } + return false; + } + @Override public int hashCode() { + return predicate.hashCode(); + } + @Override public String toString() { + return "forPredicate(" + predicate + ")"; + } + private static final long serialVersionUID = 0; + } + + /** + * Creates a function that returns {@code value} for any input. + * + * @param value the constant value for the function to return + * @return a function that always returns {@code value} + */ + public static Function constant(@Nullable E value) { + return new ConstantFunction(value); + } + + private static class ConstantFunction + implements Function, Serializable { + private final E value; + + public ConstantFunction(@Nullable E value) { + this.value = value; + } + public E apply(Object from) { + return value; + } + @Override public boolean equals(Object obj) { + if (obj instanceof ConstantFunction) { + ConstantFunction that = (ConstantFunction) obj; + return Objects.equal(value, that.value); + } + return false; + } + @Override public int hashCode() { + return (value == null) ? 0 : value.hashCode(); + } + @Override public String toString() { + return "constant(" + value + ")"; + } + private static final long serialVersionUID = 0; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Joiner.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Joiner.java new file mode 100644 index 00000000000..ff47e1a08d5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Joiner.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * An object which joins pieces of text (specified as an array, {@link + * Iterable}, varargs or even a {@link Map}) with a separator. It either + * appends the results to an {@link Appendable} or returns them as a {@link + * String}. Example:

   {@code
+ *
+ *   Joiner joiner = Joiner.on("; ").skipNulls();
+ *    . . .
+ *   return joiner.join("Harry", null, "Ron", "Hermione");}
+ * + * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input + * elements are converted to strings using {@link Object#toString()} before + * being appended. + * + *

If neither {@link #skipNulls()} nor {@link #useForNull(String)} is + * specified, the joining methods will throw {@link NullPointerException} if any + * given element is null. + * + * @author Kevin Bourrillion + */ +@GwtCompatible public class Joiner { + /** + * Returns a joiner which automatically places {@code separator} between + * consecutive elements. + */ + public static Joiner on(String separator) { + return new Joiner(separator); + } + + /** + * Returns a joiner which automatically places {@code separator} between + * consecutive elements. + */ + public static Joiner on(char separator) { + return new Joiner(String.valueOf(separator)); + } + + private final String separator; + + private Joiner(String separator) { + this.separator = checkNotNull(separator); + } + + private Joiner(Joiner prototype) { + this.separator = prototype.separator; + } + + /** + * Appends the string representation of each of {@code parts}, using the + * previously configured separator between each, to {@code appendable}. + */ + public A appendTo(A appendable, Iterable parts) + throws IOException { + checkNotNull(appendable); + Iterator iterator = parts.iterator(); + if (iterator.hasNext()) { + appendable.append(toString(iterator.next())); + while (iterator.hasNext()) { + appendable.append(separator); + appendable.append(toString(iterator.next())); + } + } + return appendable; + } + + /** + * Appends the string representation of each of {@code parts}, using the + * previously configured separator between each, to {@code appendable}. + */ + public final A appendTo( + A appendable, Object[] parts) throws IOException { + return appendTo(appendable, Arrays.asList(parts)); + } + + /** + * Appends to {@code appendable} the string representation of each of the + * remaining arguments. + */ + public final A appendTo(A appendable, + @Nullable Object first, @Nullable Object second, Object... rest) + throws IOException { + return appendTo(appendable, iterable(first, second, rest)); + } + + /** + * Appends the string representation of each of {@code parts}, using the + * previously configured separator between each, to {@code builder}. Identical + * to {@link #appendTo(Appendable, Iterable)}, except that it does not throw + * {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, Iterable parts) + { + try { + appendTo((Appendable) builder, parts); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return builder; + } + + /** + * Appends the string representation of each of {@code parts}, using the + * previously configured separator between each, to {@code builder}. Identical + * to {@link #appendTo(Appendable, Iterable)}, except that it does not throw + * {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { + return appendTo(builder, Arrays.asList(parts)); + } + + /** + * Appends to {@code builder} the string representation of each of the + * remaining arguments. Identical to {@link #appendTo(Appendable, Object, + * Object, Object[])}, except that it does not throw {@link IOException}. + */ + public final StringBuilder appendTo(StringBuilder builder, + @Nullable Object first, @Nullable Object second, Object... rest) { + return appendTo(builder, iterable(first, second, rest)); + } + + /** + * Returns a string containing the string representation of each of {@code + * parts}, using the previously configured separator between each. + */ + public final String join(Iterable parts) { + return appendTo(new StringBuilder(), parts).toString(); + } + + /** + * Returns a string containing the string representation of each of {@code + * parts}, using the previously configured separator between each. + */ + public final String join(Object[] parts) { + return join(Arrays.asList(parts)); + } + + /** + * Returns a string containing the string representation of each argument, + * using the previously configured separator between each. + */ + public final String join( + @Nullable Object first, @Nullable Object second, Object... rest) { + return join(iterable(first, second, rest)); + } + + /** + * Returns a joiner with the same behavior as this one, except automatically + * substituting {@code nullText} for any provided null elements. + */ + public Joiner useForNull(final String nullText) { + checkNotNull(nullText); + return new Joiner(this) { + @Override CharSequence toString(Object part) { + return (part == null) ? nullText : Joiner.this.toString(part); + } + @Override public Joiner useForNull(String nullText) { + checkNotNull(nullText); // weird, just to satisfy NullPointerTester! + // TODO: fix that? + throw new UnsupportedOperationException("already specified useForNull"); + } + @Override public Joiner skipNulls() { + throw new UnsupportedOperationException("already specified useForNull"); + } + }; + } + + /** + * Returns a joiner with the same behavior as this joiner, except + * automatically skipping over any provided null elements. + */ + public Joiner skipNulls() { + return new Joiner(this) { + @Override public A appendTo( + A appendable, Iterable parts) throws IOException { + checkNotNull(appendable, "appendable"); + checkNotNull(parts, "parts"); + Iterator iterator = parts.iterator(); + while (iterator.hasNext()) { + Object part = iterator.next(); + if (part != null) { + appendable.append(Joiner.this.toString(part)); + break; + } + } + while (iterator.hasNext()) { + Object part = iterator.next(); + if (part != null) { + appendable.append(separator); + appendable.append(Joiner.this.toString(part)); + } + } + return appendable; + } + @Override public Joiner useForNull(String nullText) { + checkNotNull(nullText); // weird, just to satisfy NullPointerTester! + throw new UnsupportedOperationException("already specified skipNulls"); + } + @Override public MapJoiner withKeyValueSeparator(String kvs) { + checkNotNull(kvs); // weird, just to satisfy NullPointerTester! + throw new UnsupportedOperationException( + "can't use .skipNulls() with maps"); + } + }; + } + + /** + * Returns a {@code MapJoiner} using the given key-value separator, and the + * same configuration as this {@code Joiner} otherwise. + */ + public MapJoiner withKeyValueSeparator(String keyValueSeparator) { + return new MapJoiner(this, checkNotNull(keyValueSeparator)); + } + + /** + * An object that joins map entries in the same manner as {@code Joiner} joins + * iterables and arrays. + */ + public static class MapJoiner { + private Joiner joiner; + private String keyValueSeparator; + + private MapJoiner(Joiner joiner, String keyValueSeparator) { + this.joiner = joiner; + this.keyValueSeparator = keyValueSeparator; + } + + /** + * Appends the string representation of each entry of {@code map}, using the + * previously configured separator and key-value separator, to {@code + * appendable}. + */ + public A appendTo(A appendable, Map map) + throws IOException { + checkNotNull(appendable); + Iterator> iterator = map.entrySet().iterator(); + if (iterator.hasNext()) { + Entry entry = iterator.next(); + appendable.append(joiner.toString(entry.getKey())); + appendable.append(keyValueSeparator); + appendable.append(joiner.toString(entry.getValue())); + while (iterator.hasNext()) { + appendable.append(joiner.separator); + Entry e = iterator.next(); + appendable.append(joiner.toString(e.getKey())); + appendable.append(keyValueSeparator); + appendable.append(joiner.toString(e.getValue())); + } + } + return appendable; + } + + /** + * Appends the string representation of each entry of {@code map}, using the + * previously configured separator and key-value separator, to {@code + * builder}. Identical to {@link #appendTo(Appendable, Map)}, except that it + * does not throw {@link IOException}. + */ + public StringBuilder appendTo(StringBuilder builder, Map map) { + try { + appendTo((Appendable) builder, map); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + return builder; + } + + /** + * Returns a string containing the string representation of each entry of + * {@code map}, using the previously configured separator and key-value + * separator. + */ + public String join(Map map) { + return appendTo(new StringBuilder(), map).toString(); + } + + /** + * Returns a map joiner with the same behavior as this one, except + * automatically substituting {@code nullText} for any provided null keys or + * values. + */ + public MapJoiner useForNull(String nullText) { + return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); + } + } + + CharSequence toString(Object part) { + return (part instanceof CharSequence) + ? (CharSequence) part + : part.toString(); + } + + private static Iterable iterable( + final Object first, final Object second, final Object[] rest) { + checkNotNull(rest); + return new AbstractList() { + @Override public int size() { + return rest.length + 2; + } + @Override public Object get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + return rest[index - 2]; + } + } + }; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Objects.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Objects.java new file mode 100644 index 00000000000..6135693030b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Objects.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Arrays; + +import javax.annotation.Nullable; + +/** + * Helper functions that can operate on any {@code Object}. + * + * @author Laurence Gonsalves + */ +@GwtCompatible +public final class Objects { + private Objects() {} + + /** + * Determines whether two possibly-null objects are equal. Returns: + * + *
    + *
  • {@code true} if {@code a} and {@code b} are both null. + *
  • {@code true} if {@code a} and {@code b} are both non-null and they are + * equal according to {@link Object#equals(Object)}. + *
  • {@code false} in all other situations. + *
+ * + *

This assumes that any non-null objects passed to this function conform + * to the {@code equals()} contract. + */ + public static boolean equal(@Nullable Object a, @Nullable Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Generates a hash code for multiple values. The hash code is generated by + * calling {@link Arrays#hashCode(Object[])}. + * + *

This is useful for implementing {@link Object#hashCode()}. For example, + * in an object that has three properties, {@code x}, {@code y}, and + * {@code z}, one could write: + *

+   * public int hashCode() {
+   *   return Objects.hashCode(getX(), getY(), getZ());
+   * }
+ * + * Warning: When a single object is supplied, the returned hash code + * does not equal the hash code of that object. + */ + public static int hashCode(Object... objects) { + return Arrays.hashCode(objects); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Platform.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Platform.java new file mode 100644 index 00000000000..30cb69e8c91 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Platform.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Jesse Wilson + */ +@GwtCompatible(emulated = true) +final class Platform { + private Platform() {} + + /** + * Calls {@link Class#isInstance(Object)}. + * + *

This method is not supported in GWT yet. + */ + static boolean isInstance(Class clazz, Object obj) { + return clazz.isInstance(obj); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Preconditions.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Preconditions.java new file mode 100644 index 00000000000..f72add517d9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Preconditions.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; + +import java.util.NoSuchElementException; + +/** + * Simple static methods to be called at the start of your own methods to verify + * correct arguments and state. This allows constructs such as + *

+ *     if (count <= 0) {
+ *       throw new IllegalArgumentException("must be positive: " + count);
+ *     }
+ * + * to be replaced with the more compact + *
+ *     checkArgument(count > 0, "must be positive: %s", count);
+ * + * Note that the sense of the expression is inverted; with {@code Preconditions} + * you declare what you expect to be true, just as you do with an + * + * {@code assert} or a JUnit {@code assertTrue} call. + * + *

Warning: only the {@code "%s"} specifier is recognized as a + * placeholder in these messages, not the full range of {@link + * String#format(String, Object[])} specifiers. + * + *

Take care not to confuse precondition checking with other similar types + * of checks! Precondition exceptions -- including those provided here, but also + * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link + * UnsupportedOperationException} and others -- are used to signal that the + * calling method has made an error. This tells the caller that it should + * not have invoked the method when it did, with the arguments it did, or + * perhaps ever. Postcondition or other invariant failures should not throw + * these types of exceptions. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public final class Preconditions { + private Preconditions() {} + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkArgument(boolean expression, + String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalStateException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkState(boolean expression, + String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference, String errorMessageTemplate, + Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException( + format(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /** + * Ensures that {@code index} specifies a valid element in an array, + * list or string of size {@code size}. An element index may range from zero, + * inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list + * or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not + * less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, + * list or string of size {@code size}. An element index may range from zero, + * inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list + * or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not + * less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkElementIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); + } + return index; + } + + private static String badElementIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index >= size + return format("%s (%s) must be less than size (%s)", desc, index, size); + } + } + + /** + * Ensures that {@code index} specifies a valid position in an array, + * list or string of size {@code size}. A position index may range from zero + * to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list + * or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkPositionIndex(int index, int size) { + return checkPositionIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid position in an array, + * list or string of size {@code size}. A position index may range from zero + * to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list + * or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static int checkPositionIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + return index; + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return format("%s (%s) must not be greater than size (%s)", + desc, index, size); + } + } + + /** + * Ensures that {@code start} and {@code end} specify a valid positions + * in an array, list or string of size {@code size}, and are in order. A + * position index may range from zero to {@code size}, inclusive. + * + * @param start a user-supplied index identifying a starting position in an + * array, list or string + * @param end a user-supplied index identifying a ending position in an array, + * list or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if either index is negative or is + * greater than {@code size}, or if {@code end} is less than {@code start} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + if (start < 0 || start > size) { + return badPositionIndex(start, size, "start index"); + } + if (end < 0 || end > size) { + return badPositionIndex(end, size, "end index"); + } + // end < start + return format("end index (%s) must not be less than start index (%s)", + end, start); + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position - the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments will + * be appended to the end of the formatted message in square braces. + * + * @param template a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + */ + @VisibleForTesting static String format(String template, Object... args) { + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder( + template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append("]"); + } + + return builder.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Predicate.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Predicate.java new file mode 100644 index 00000000000..2e99ab79733 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Predicate.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import javax.annotation.Nullable; + +/** + * Determines a true or false value for a given input. For example, a + * {@code RegexPredicate} might implement {@code Predicate}, and return + * {@code true} for any string that matches its given regular expression. + * + *

Implementations which may cause side effects upon evaluation are strongly + * encouraged to state this fact clearly in their API documentation. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public interface Predicate { + + /* + * This interface does not extend Function because doing so would + * let predicates return null. + */ + + /** + * Applies this predicate to the given object. + * + * @param input the input that the predicate should act on + * @return the value of this predicate when applied to the input {@code t} + */ + boolean apply(@Nullable T input); + + /** + * Indicates whether some other object is equal to this {@code Predicate}. + * This method can return {@code true} only if the specified object is + * also a {@code Predicate} and, for every input object {@code input}, it + * returns exactly the same value. Thus, {@code predicate1.equals(predicate2)} + * implies that either {@code predicate1.apply(input)} and + * {@code predicate2.apply(input)} are both {@code true} or both + * {@code false}. + * + *

Note that it is always safe not to override + * {@link Object#equals}. + */ + boolean equals(@Nullable Object obj); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Predicates.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Predicates.java new file mode 100644 index 00000000000..506219e9bfb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Predicates.java @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Contains static factory methods for creating {@code Predicate} instances. + * + *

All methods returns serializable predicates as long as they're given + * serializable parameters. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public final class Predicates { + private Predicates() {} + + // TODO: considering having these implement a VisitablePredicate interface + // which specifies an accept(PredicateVisitor) method. + + /** + * Returns a predicate that always evaluates to {@code true}. + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") + public static Predicate alwaysTrue() { + return (Predicate) AlwaysTruePredicate.INSTANCE; + } + + /** + * Returns a predicate that always evaluates to {@code false}. + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") + public static Predicate alwaysFalse() { + return (Predicate) AlwaysFalsePredicate.INSTANCE; + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is null. + */ + @SuppressWarnings("unchecked") + public static Predicate isNull() { + return (Predicate) IsNullPredicate.INSTANCE; + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is not null. + */ + @SuppressWarnings("unchecked") + public static Predicate notNull() { + return (Predicate) NotNullPredicate.INSTANCE; + } + + /** + * Returns a predicate that evaluates to {@code true} if the given predicate + * evaluates to {@code false}. + */ + public static Predicate not(Predicate predicate) { + return new NotPredicate(predicate); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. It defensively copies the iterable passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * true}. + */ + public static Predicate and( + Iterable> components) { + return new AndPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if each of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. It defensively copies the array passed in, so future + * changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * true}. + */ + public static Predicate and(Predicate... components) { + return new AndPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if both of its + * components evaluate to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as a false + * predicate is found. + */ + public static Predicate and(Predicate first, + Predicate second) { + return new AndPredicate(Predicates.asList( + checkNotNull(first), checkNotNull(second))); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as as soon as a + * true predicate is found. It defensively copies the iterable passed in, so + * future changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * false}. + */ + public static Predicate or( + Iterable> components) { + return new OrPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if any one of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as as soon as a + * true predicate is found. It defensively copies the array passed in, so + * future changes to it won't alter the behavior of this predicate. If {@code + * components} is empty, the returned predicate will always evaluate to {@code + * false}. + */ + public static Predicate or(Predicate... components) { + return new OrPredicate(defensiveCopy(components)); + } + + /** + * Returns a predicate that evaluates to {@code true} if either of its + * components evaluates to {@code true}. The components are evaluated in + * order, and evaluation will be "short-circuited" as soon as as soon as a + * true predicate is found. + */ + public static Predicate or(Predicate first, + Predicate second) { + return new OrPredicate(Predicates.asList( + checkNotNull(first), checkNotNull(second))); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being + * tested {@code equals()} the given target or both are null. + */ + public static Predicate equalTo(@Nullable T target) { + // TODO: Change signature to return Predicate. + return (target == null) + ? Predicates.isNull() + : new IsEqualToPredicate(target); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object being + * tested is an instance of the given class. If the object being tested + * is {@code null} this predicate evaluates to {@code false}. + * + *

If you want to filter an {@code Iterable} to narrow its type, consider + * using {@link org.elasticsearch.util.gcommon.collect.Iterables#filter(Iterable, Class)} + * in preference. + */ + @GwtIncompatible("Class.isInstance") + public static Predicate instanceOf(Class clazz) { + return new InstanceOfPredicate(clazz); + } + + /** + * Returns a predicate that evaluates to {@code true} if the object reference + * being tested is a member of the given collection. It does not defensively + * copy the collection passed in, so future changes to it will alter the + * behavior of the predicate. + * + * This method can technically accept any Collection, but using a typed + * collection helps prevent bugs. This approach doesn't block any potential + * users since it is always possible to use {@code Predicates.in()}. + * + * @param target the collection that may contain the function input + */ + public static Predicate in(Collection target) { + return new InPredicate(target); + } + + /** + * Returns the composition of a function and a predicate. For every {@code x}, + * the generated predicate returns {@code predicate(function(x))}. + * + * @return the composition of the provided function and predicate + */ + public static Predicate compose( + Predicate predicate, Function function) { + return new CompositionPredicate(predicate, function); + } + + /** @see Predicates#alwaysTrue() */ + // Package private for GWT serialization. + enum AlwaysTruePredicate implements Predicate { + INSTANCE; + + public boolean apply(Object o) { + return true; + } + @Override public String toString() { + return "AlwaysTrue"; + } + } + + /** @see Predicates#alwaysFalse() */ + // Package private for GWT serialization. + enum AlwaysFalsePredicate implements Predicate { + INSTANCE; + + public boolean apply(Object o) { + return false; + } + @Override public String toString() { + return "AlwaysFalse"; + } + } + + /** @see Predicates#not(Predicate) */ + private static class NotPredicate + implements Predicate, Serializable { + private final Predicate predicate; + + private NotPredicate(Predicate predicate) { + this.predicate = checkNotNull(predicate); + } + public boolean apply(T t) { + return !predicate.apply(t); + } + @Override public int hashCode() { + return ~predicate.hashCode(); /* Invert all bits. */ + } + @Override public boolean equals(Object obj) { + if (obj instanceof NotPredicate) { + NotPredicate that = (NotPredicate) obj; + return predicate.equals(that.predicate); + } + return false; + } + @Override public String toString() { + return "Not(" + predicate.toString() + ")"; + } + private static final long serialVersionUID = 0; + } + + private static final Joiner commaJoiner = Joiner.on(","); + + /** @see Predicates#and(Iterable) */ + private static class AndPredicate + implements Predicate, Serializable { + private final Iterable> components; + + private AndPredicate(Iterable> components) { + this.components = components; + } + public boolean apply(T t) { + for (Predicate predicate : components) { + if (!predicate.apply(t)) { + return false; + } + } + return true; + } + @Override public int hashCode() { + int result = -1; /* Start with all bits on. */ + for (Predicate predicate : components) { + result &= predicate.hashCode(); + } + return result; + } + @Override public boolean equals(Object obj) { + if (obj instanceof AndPredicate) { + AndPredicate that = (AndPredicate) obj; + return iterableElementsEqual(components, that.components); + } + return false; + } + @Override public String toString() { + return "And(" + commaJoiner.join(components) + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#or(Iterable) */ + private static class OrPredicate + implements Predicate, Serializable { + private final Iterable> components; + + private OrPredicate(Iterable> components) { + this.components = components; + } + public boolean apply(T t) { + for (Predicate predicate : components) { + if (predicate.apply(t)) { + return true; + } + } + return false; + } + @Override public int hashCode() { + int result = 0; /* Start with all bits off. */ + for (Predicate predicate : components) { + result |= predicate.hashCode(); + } + return result; + } + @Override public boolean equals(Object obj) { + if (obj instanceof OrPredicate) { + OrPredicate that = (OrPredicate) obj; + return iterableElementsEqual(components, that.components); + } + return false; + } + @Override public String toString() { + return "Or(" + commaJoiner.join(components) + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#equalTo(Object) */ + private static class IsEqualToPredicate + implements Predicate, Serializable { + private final T target; + + private IsEqualToPredicate(T target) { + this.target = target; + } + public boolean apply(T t) { + return target.equals(t); + } + @Override public int hashCode() { + return target.hashCode(); + } + @Override public boolean equals(Object obj) { + if (obj instanceof IsEqualToPredicate) { + IsEqualToPredicate that = (IsEqualToPredicate) obj; + return target.equals(that.target); + } + return false; + } + @Override public String toString() { + return "IsEqualTo(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#instanceOf(Class) */ + private static class InstanceOfPredicate + implements Predicate, Serializable { + private final Class clazz; + + private InstanceOfPredicate(Class clazz) { + this.clazz = checkNotNull(clazz); + } + public boolean apply(Object o) { + return Platform.isInstance(clazz, o); + } + @Override public int hashCode() { + return clazz.hashCode(); + } + @Override public boolean equals(Object obj) { + if (obj instanceof InstanceOfPredicate) { + InstanceOfPredicate that = (InstanceOfPredicate) obj; + return clazz == that.clazz; + } + return false; + } + @Override public String toString() { + return "IsInstanceOf(" + clazz.getName() + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#isNull() */ + // enum singleton pattern + private enum IsNullPredicate implements Predicate { + INSTANCE; + + public boolean apply(Object o) { + return o == null; + } + @Override public String toString() { + return "IsNull"; + } + } + + /** @see Predicates#notNull() */ + // enum singleton pattern + private enum NotNullPredicate implements Predicate { + INSTANCE; + + public boolean apply(Object o) { + return o != null; + } + @Override public String toString() { + return "NotNull"; + } + } + + /** @see Predicates#in(Collection) */ + private static class InPredicate + implements Predicate, Serializable { + private final Collection target; + + private InPredicate(Collection target) { + this.target = checkNotNull(target); + } + + public boolean apply(T t) { + try { + return target.contains(t); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean equals(Object obj) { + if (obj instanceof InPredicate) { + InPredicate that = (InPredicate) obj; + return target.equals(that.target); + } + return false; + } + + @Override public int hashCode() { + return target.hashCode(); + } + + @Override public String toString() { + return "In(" + target + ")"; + } + private static final long serialVersionUID = 0; + } + + /** @see Predicates#compose(Predicate, Function) */ + private static class CompositionPredicate + implements Predicate, Serializable { + final Predicate p; + final Function f; + + private CompositionPredicate(Predicate p, Function f) { + this.p = checkNotNull(p); + this.f = checkNotNull(f); + } + + public boolean apply(A a) { + return p.apply(f.apply(a)); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof CompositionPredicate) { + CompositionPredicate that = (CompositionPredicate) obj; + return f.equals(that.f) && p.equals(that.p); + } + return false; + } + + @Override public int hashCode() { + /* + * TODO: To leave the door open for future enhancement, this + * calculation should be coordinated with the hashCode() method of the + * corresponding composition method in Functions. To construct the + * composition: + * predicate(function2(function1(x))) + * + * There are two different ways of composing it: + * compose(predicate, compose(function2, function1)) + * compose(compose(predicate, function2), function1) + * + * It would be nice if these could be equal. + */ + return f.hashCode() ^ p.hashCode(); + } + + @Override public String toString() { + return p.toString() + "(" + f.toString() + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Determines whether the two Iterables contain equal elements. More + * specifically, this method returns {@code true} if {@code iterable1} and + * {@code iterable2} contain the same number of elements and every element of + * {@code iterable1} is equal to the corresponding element of {@code + * iterable2}. + * + *

This is not a general-purpose method; it assumes that the iterations + * contain no {@code null} elements. + */ + private static boolean iterableElementsEqual( + Iterable iterable1, Iterable iterable2) { + Iterator iterator1 = iterable1.iterator(); + Iterator iterator2 = iterable2.iterator(); + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + if (!iterator1.next().equals(iterator2.next())) { + return false; + } + } + return !iterator2.hasNext(); + } + + @SuppressWarnings("unchecked") + private static List> asList( + Predicate first, Predicate second) { + return Arrays.>asList(first, second); + } + + private static List defensiveCopy(T... array) { + return defensiveCopy(Arrays.asList(array)); + } + + static List defensiveCopy(Iterable iterable) { + ArrayList list = new ArrayList(); + for (T element : iterable) { + list.add(checkNotNull(element)); + } + return list; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Supplier.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Supplier.java new file mode 100644 index 00000000000..9450e36bc55 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Supplier.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * A class that can supply objects of a single type. Semantically, this could + * be a factory, generator, builder, closure, or something else entirely. No + * guarantees are implied by this interface. + * + * @author Harry Heymann + */ +@GwtCompatible +public interface Supplier { + /** + * Retrieves an instance of the appropriate type. The returned object may or + * may not be a new instance, depending on the implementation. + * + * @return an instance of the appropriate type + */ + public T get(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Suppliers.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Suppliers.java new file mode 100644 index 00000000000..54f23945112 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/Suppliers.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base; + +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * Useful suppliers. + * + *

All methods return serializable suppliers as long as they're given + * serializable parameters. + * + * @author Laurence Gonsalves + * @author Harry Heymann + */ +public final class Suppliers { + private Suppliers() {} + + /** + * Returns a new supplier which is the composition of the provided function + * and supplier. In other words, the new supplier's value will be computed by + * retrieving the value from {@code first}, and then applying + * {@code function} to that value. Note that the resulting supplier will not + * call {@code first} or invoke {@code function} until it is called. + */ + public static Supplier compose( + Function function, Supplier first) { + Preconditions.checkNotNull(function); + Preconditions.checkNotNull(first); + return new SupplierComposition(function, first); + } + + private static class SupplierComposition + implements Supplier, Serializable { + final Function function; + final Supplier first; + + SupplierComposition(Function function, + Supplier first) { + this.function = function; + this.first = first; + } + public T get() { + return function.apply(first.get()); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier which caches the instance retrieved during the first + * call to {@code get()} and returns that value on subsequent calls to + * {@code get()}. See: + * memoization + * + *

The returned supplier is thread-safe. The supplier's serialized form + * does not contain the cached value, which will be recalculated when {@code + * get()} is called on the reserialized instance. + */ + public static Supplier memoize(Supplier delegate) { + return new MemoizingSupplier(Preconditions.checkNotNull(delegate)); + } + + @VisibleForTesting static class MemoizingSupplier + implements Supplier, Serializable { + final Supplier delegate; + transient boolean initialized; + transient T value; + + MemoizingSupplier(Supplier delegate) { + this.delegate = delegate; + } + + public synchronized T get() { + if (!initialized) { + value = delegate.get(); + initialized = true; + } + return value; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier that always supplies {@code instance}. + */ + public static Supplier ofInstance(@Nullable T instance) { + return new SupplierOfInstance(instance); + } + + private static class SupplierOfInstance + implements Supplier, Serializable { + final T instance; + + SupplierOfInstance(T instance) { + this.instance = instance; + } + public T get() { + return instance; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a supplier whose {@code get()} method synchronizes on + * {@code delegate} before calling it, making it thread-safe. + */ + public static Supplier synchronizedSupplier(Supplier delegate) { + return new ThreadSafeSupplier(Preconditions.checkNotNull(delegate)); + } + + private static class ThreadSafeSupplier + implements Supplier, Serializable { + final Supplier delegate; + + ThreadSafeSupplier(Supplier delegate) { + this.delegate = delegate; + } + public T get() { + synchronized (delegate) { + return delegate.get(); + } + } + private static final long serialVersionUID = 0; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/internal/Finalizer.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/internal/Finalizer.java new file mode 100644 index 00000000000..11fca3ff7cd --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/internal/Finalizer.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.base.internal; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Thread that finalizes referents. All references should implement + * {@code com.google.common.base.FinalizableReference}. + * + *

While this class is public, we consider it to be *internal* and not part + * of our published API. It is public so we can access it reflectively across + * class loaders in secure environments. + * + *

This class can't depend on other Google Collections code. If we were + * to load this class in the same class loader as the rest of + * Google Collections, this thread would keep an indirect strong reference + * to the class loader and prevent it from being garbage collected. This + * poses a problem for environments where you want to throw away the class + * loader. For example, dynamically reloading a web application or unloading + * an OSGi bundle. + * + *

{@code com.google.common.base.FinalizableReferenceQueue} loads this class + * in its own class loader. That way, this class doesn't prevent the main + * class loader from getting garbage collected, and this class can detect when + * the main class loader has been garbage collected and stop itself. + */ +public class Finalizer extends Thread { + + private static final Logger logger + = Logger.getLogger(Finalizer.class.getName()); + + /** Name of FinalizableReference.class. */ + private static final String FINALIZABLE_REFERENCE + = "org.elasticsearch.util.gcommon.base.FinalizableReference"; + + /** + * Starts the Finalizer thread. FinalizableReferenceQueue calls this method + * reflectively. + * + * @param finalizableReferenceClass FinalizableReference.class + * @param frq reference to instance of FinalizableReferenceQueue that started + * this thread + * @return ReferenceQueue which Finalizer will poll + */ + public static ReferenceQueue startFinalizer( + Class finalizableReferenceClass, Object frq) { + /* + * We use FinalizableReference.class for two things: + * + * 1) To invoke FinalizableReference.finalizeReferent() + * + * 2) To detect when FinalizableReference's class loader has to be garbage + * collected, at which point, Finalizer can stop running + */ + if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE)) { + throw new IllegalArgumentException( + "Expected " + FINALIZABLE_REFERENCE + "."); + } + + Finalizer finalizer = new Finalizer(finalizableReferenceClass, frq); + finalizer.start(); + return finalizer.queue; + } + + private final WeakReference> finalizableReferenceClassReference; + private final PhantomReference frqReference; + private final ReferenceQueue queue = new ReferenceQueue(); + + private static final Field inheritableThreadLocals + = getInheritableThreadLocalsField(); + + /** Constructs a new finalizer thread. */ + private Finalizer(Class finalizableReferenceClass, Object frq) { + super(Finalizer.class.getName()); + + this.finalizableReferenceClassReference + = new WeakReference>(finalizableReferenceClass); + + // Keep track of the FRQ that started us so we know when to stop. + this.frqReference = new PhantomReference(frq, queue); + + setDaemon(true); + + try { + if (inheritableThreadLocals != null) { + inheritableThreadLocals.set(this, null); + } + } catch (Throwable t) { + logger.log(Level.INFO, "Failed to clear thread local values inherited" + + " by reference finalizer thread.", t); + } + + // TODO: Priority? + } + + /** + * Loops continuously, pulling references off the queue and cleaning them up. + */ + @SuppressWarnings("InfiniteLoopStatement") + @Override + public void run() { + try { + while (true) { + try { + cleanUp(queue.remove()); + } catch (InterruptedException e) { /* ignore */ } + } + } catch (ShutDown shutDown) { /* ignore */ } + } + + /** + * Cleans up a single reference. Catches and logs all throwables. + */ + private void cleanUp(Reference reference) throws ShutDown { + Method finalizeReferentMethod = getFinalizeReferentMethod(); + do { + /* + * This is for the benefit of phantom references. Weak and soft + * references will have already been cleared by this point. + */ + reference.clear(); + + if (reference == frqReference) { + /* + * The client no longer has a reference to the + * FinalizableReferenceQueue. We can stop. + */ + throw new ShutDown(); + } + + try { + finalizeReferentMethod.invoke(reference); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + + /* + * Loop as long as we have references available so as not to waste + * CPU looking up the Method over and over again. + */ + } while ((reference = queue.poll()) != null); + } + + /** + * Looks up FinalizableReference.finalizeReferent() method. + */ + private Method getFinalizeReferentMethod() throws ShutDown { + Class finalizableReferenceClass + = finalizableReferenceClassReference.get(); + if (finalizableReferenceClass == null) { + /* + * FinalizableReference's class loader was reclaimed. While there's a + * chance that other finalizable references could be enqueued + * subsequently (at which point the class loader would be resurrected + * by virtue of us having a strong reference to it), we should pretty + * much just shut down and make sure we don't keep it alive any longer + * than necessary. + */ + throw new ShutDown(); + } + try { + return finalizableReferenceClass.getMethod("finalizeReferent"); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + public static Field getInheritableThreadLocalsField() { + try { + Field inheritableThreadLocals + = Thread.class.getDeclaredField("inheritableThreadLocals"); + inheritableThreadLocals.setAccessible(true); + return inheritableThreadLocals; + } catch (Throwable t) { + logger.log(Level.INFO, "Couldn't access Thread.inheritableThreadLocals." + + " Reference finalizer threads will inherit thread local" + + " values."); + return null; + } + } + + /** Indicates that it's time to shut down the Finalizer. */ + @SuppressWarnings("serial") // Never serialized or thrown out of this class. + private static class ShutDown extends Exception { } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/package-info.java new file mode 100644 index 00000000000..f5faee65acc --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/base/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +/** + * Miscellaneous common util classes and annotations. + */ +package org.elasticsearch.util.gcommon.base; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractBiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractBiMap.java new file mode 100644 index 00000000000..0823e76c0a6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractBiMap.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Objects; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A general-purpose bimap implementation using any two backing {@code Map} + * instances. + * + *

Note that this class contains {@code equals()} calls that keep it from + * supporting {@code IdentityHashMap} backing maps. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +@GwtCompatible +abstract class AbstractBiMap extends ForwardingMap + implements BiMap, Serializable { + + private transient Map delegate; + private transient AbstractBiMap inverse; + + /** Package-private constructor for creating a map-backed bimap. */ + AbstractBiMap(Map forward, Map backward) { + setDelegates(forward, backward); + } + + /** Private constructor for inverse bimap. */ + private AbstractBiMap(Map backward, AbstractBiMap forward) { + delegate = backward; + inverse = forward; + } + + @Override protected Map delegate() { + return delegate; + } + + /** + * Specifies the delegate maps going in each direction. Called by the + * constructor and by subclasses during deserialization. + */ + void setDelegates(Map forward, Map backward) { + checkState(delegate == null); + checkState(inverse == null); + checkArgument(forward.isEmpty()); + checkArgument(backward.isEmpty()); + checkArgument(forward != backward); + delegate = forward; + inverse = new Inverse(backward, this); + } + + void setInverse(AbstractBiMap inverse) { + this.inverse = inverse; + } + + // Query Operations (optimizations) + + @Override public boolean containsValue(Object value) { + return inverse.containsKey(value); + } + + // Modification Operations + + @Override public V put(K key, V value) { + return putInBothMaps(key, value, false); + } + + public V forcePut(K key, V value) { + return putInBothMaps(key, value, true); + } + + private V putInBothMaps(@Nullable K key, @Nullable V value, boolean force) { + boolean containedKey = containsKey(key); + if (containedKey && Objects.equal(value, get(key))) { + return value; + } + if (force) { + inverse().remove(value); + } else { + checkArgument(!containsValue(value), "value already present: %s", value); + } + V oldValue = delegate.put(key, value); + updateInverseMap(key, containedKey, oldValue, value); + return oldValue; + } + + private void updateInverseMap( + K key, boolean containedKey, V oldValue, V newValue) { + if (containedKey) { + removeFromInverseMap(oldValue); + } + inverse.delegate.put(newValue, key); + } + + @Override public V remove(Object key) { + return containsKey(key) ? removeFromBothMaps(key) : null; + } + + private V removeFromBothMaps(Object key) { + V oldValue = delegate.remove(key); + removeFromInverseMap(oldValue); + return oldValue; + } + + private void removeFromInverseMap(V oldValue) { + inverse.delegate.remove(oldValue); + } + + // Bulk Operations + + @Override public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override public void clear() { + delegate.clear(); + inverse.delegate.clear(); + } + + // Views + + public BiMap inverse() { + return inverse; + } + + private transient Set keySet; + + @Override public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = new KeySet() : result; + } + + private class KeySet extends ForwardingSet { + @Override protected Set delegate() { + return delegate.keySet(); + } + + @Override public void clear() { + AbstractBiMap.this.clear(); + } + + @Override public boolean remove(Object key) { + if (!contains(key)) { + return false; + } + removeFromBothMaps(key); + return true; + } + + @Override public boolean removeAll(Collection keysToRemove) { + return Iterators.removeAll(iterator(), keysToRemove); + } + + @Override public boolean retainAll(Collection keysToRetain) { + return Iterators.retainAll(iterator(), keysToRetain); + } + + @Override public Iterator iterator() { + final Iterator> iterator = delegate.entrySet().iterator(); + return new Iterator() { + Entry entry; + + public boolean hasNext() { + return iterator.hasNext(); + } + public K next() { + entry = iterator.next(); + return entry.getKey(); + } + public void remove() { + checkState(entry != null); + V value = entry.getValue(); + iterator.remove(); + removeFromInverseMap(value); + } + }; + } + } + + private transient Set valueSet; + + @Override public Set values() { + /* + * We can almost reuse the inverse's keySet, except we have to fix the + * iteration order so that it is consistent with the forward map. + */ + Set result = valueSet; + return (result == null) ? valueSet = new ValueSet() : result; + } + + private class ValueSet extends ForwardingSet { + final Set valuesDelegate = inverse.keySet(); + + @Override protected Set delegate() { + return valuesDelegate; + } + + @Override public Iterator iterator() { + final Iterator iterator = delegate.values().iterator(); + return new Iterator() { + V valueToRemove; + + /*@Override*/ public boolean hasNext() { + return iterator.hasNext(); + } + + /*@Override*/ public V next() { + return valueToRemove = iterator.next(); + } + + /*@Override*/ public void remove() { + iterator.remove(); + removeFromInverseMap(valueToRemove); + } + }; + } + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + + @Override public String toString() { + return Iterators.toString(iterator()); + } + } + + private transient Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + private class EntrySet extends ForwardingSet> { + final Set> esDelegate = delegate.entrySet(); + + @Override protected Set> delegate() { + return esDelegate; + } + + @Override public void clear() { + AbstractBiMap.this.clear(); + } + + @Override public boolean remove(Object object) { + if (!esDelegate.remove(object)) { + return false; + } + Entry entry = (Entry) object; + inverse.delegate.remove(entry.getValue()); + return true; + } + + @Override public Iterator> iterator() { + final Iterator> iterator = esDelegate.iterator(); + return new Iterator>() { + Entry entry; + + /*@Override*/ public boolean hasNext() { + return iterator.hasNext(); + } + + /*@Override*/ public Entry next() { + entry = iterator.next(); + final Entry finalEntry = entry; + + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return finalEntry; + } + + @Override public V setValue(V value) { + // Preconditions keep the map and inverse consistent. + checkState(contains(this), "entry no longer in map"); + // similar to putInBothMaps, but set via entry + if (Objects.equal(value, getValue())) { + return value; + } + checkArgument(!containsValue(value), + "value already present: %s", value); + V oldValue = finalEntry.setValue(value); + checkState(Objects.equal(value, get(getKey())), + "entry no longer in map"); + updateInverseMap(getKey(), true, oldValue, value); + return oldValue; + } + }; + } + + /*@Override*/ public void remove() { + checkState(entry != null); + V value = entry.getValue(); + iterator.remove(); + removeFromInverseMap(value); + } + }; + } + + // See java.util.Collections.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + @Override public boolean containsAll(Collection c) { + return Collections2.containsAll(this, c); + } + @Override public boolean removeAll(Collection c) { + return Iterators.removeAll(iterator(), c); + } + @Override public boolean retainAll(Collection c) { + return Iterators.retainAll(iterator(), c); + } + } + + /** The inverse of any other {@code AbstractBiMap} subclass. */ + private static class Inverse extends AbstractBiMap { + private Inverse(Map backward, AbstractBiMap forward) { + super(backward, forward); + } + + /* + * Serialization stores the forward bimap, the inverse of this inverse. + * Deserialization calls inverse() on the forward bimap and returns that + * inverse. + * + * If a bimap and its inverse are serialized together, the deserialized + * instances have inverse() methods that return the other. + */ + + /** + * @serialData the forward bimap + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(inverse()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + setInverse((AbstractBiMap) stream.readObject()); + } + + Object readResolve() { + return inverse().inverse(); + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractIterator.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractIterator.java new file mode 100644 index 00000000000..d85232afb32 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractIterator.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; + +import java.util.NoSuchElementException; + +/** + * This class provides a skeletal implementation of the {@code Iterator} + * interface, to make this interface easier to implement for certain types of + * data sources. + * + *

{@code Iterator} requires its implementations to support querying the + * end-of-data status without changing the iterator's state, using the {@link + * #hasNext} method. But many data sources, such as {@link + * java.io.Reader#read()}), do not expose this information; the only way to + * discover whether there is any data left is by trying to retrieve it. These + * types of data sources are ordinarily difficult to write iterators for. But + * using this class, one must implement only the {@link #computeNext} method, + * and invoke the {@link #endOfData} method when appropriate. + * + *

Another example is an iterator that skips over null elements in a backing + * iterator. This could be implemented as:

   {@code
+ *
+ *   public static Iterator skipNulls(final Iterator in) {
+ *     return new AbstractIterator() {
+ *       protected String computeNext() {
+ *         while (in.hasNext()) {
+ *           String s = in.next();
+ *           if (s != null) {
+ *             return s;
+ *           }
+ *         }
+ *         return endOfData();
+ *       }
+ *     };
+ *   }}
+ * + * This class supports iterators that include null elements. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public abstract class AbstractIterator extends UnmodifiableIterator { + private State state = State.NOT_READY; + + private enum State { + /** We have computed the next element and haven't returned it yet. */ + READY, + + /** We haven't yet computed or have already returned the element. */ + NOT_READY, + + /** We have reached the end of the data and are finished. */ + DONE, + + /** We've suffered an exception and are kaput. */ + FAILED, + } + + private T next; + + /** + * Returns the next element. Note: the implementation must call {@link + * #endOfData()} when there are no elements left in the iteration. Failure to + * do so could result in an infinite loop. + * + *

The initial invocation of {@link #hasNext()} or {@link #next()} calls + * this method, as does the first invocation of {@code hasNext} or {@code + * next} following each successful call to {@code next}. Once the + * implementation either invokes {@code endOfData} or throws an exception, + * {@code computeNext} is guaranteed to never be called again. + * + *

If this method throws an exception, it will propagate outward to the + * {@code hasNext} or {@code next} invocation that invoked this method. Any + * further attempts to use the iterator will result in an {@link + * IllegalStateException}. + * + *

The implementation of this method may not invoke the {@code hasNext}, + * {@code next}, or {@link #peek()} methods on this instance; if it does, an + * {@code IllegalStateException} will result. + * + * @return the next element if there was one. If {@code endOfData} was called + * during execution, the return value will be ignored. + * @throws RuntimeException if any unrecoverable error happens. This exception + * will propagate outward to the {@code hasNext()}, {@code next()}, or + * {@code peek()} invocation that invoked this method. Any further + * attempts to use the iterator will result in an + * {@link IllegalStateException}. + */ + protected abstract T computeNext(); + + /** + * Implementations of {@code computeNext} must invoke this method when + * there are no elements left in the iteration. + * + * @return {@code null}; a convenience so your {@link #computeNext} + * implementation can use the simple statement {@code return endOfData();} + */ + protected final T endOfData() { + state = State.DONE; + return null; + } + + public final boolean hasNext() { + checkState(state != State.FAILED); + switch (state) { + case DONE: + return false; + case READY: + return true; + default: + } + return tryToComputeNext(); + } + + private boolean tryToComputeNext() { + state = State.FAILED; // temporary pessimism + next = computeNext(); + if (state != State.DONE) { + state = State.READY; + return true; + } + return false; + } + + public final T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = State.NOT_READY; + return next; + } + + /** + * Returns the next element in the iteration without advancing the iteration, + * according to the contract of {@link PeekingIterator#peek()}. + * + *

Implementations of {@code AbstractIterator} that wish to expose this + * functionality should implement {@code PeekingIterator}. + */ + public final T peek() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return next; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractListMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractListMultimap.java new file mode 100644 index 00000000000..820f93638f9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractListMultimap.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link ListMultimap} interface. It's a wrapper + * around {@link AbstractMultimap} that converts the returned collections into + * {@code Lists}. The {@link #createCollection} method must return a {@code + * List}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractListMultimap + extends AbstractMultimap implements ListMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractListMultimap(Map> map) { + super(map); + } + + @Override abstract List createCollection(); + + @Override public List get(@Nullable K key) { + return (List) super.get(key); + } + + @Override public List removeAll(@Nullable Object key) { + return (List) super.removeAll(key); + } + + @Override public List replaceValues( + @Nullable K key, Iterable values) { + return (List) super.replaceValues(key, values); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + @Override public boolean put(@Nullable K key, @Nullable V value) { + return super.put(key, value); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + */ + @Override public boolean equals(@Nullable Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 6588350623831699109L; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMapBasedMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMapBasedMultiset.java new file mode 100644 index 00000000000..02bde7e8772 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMapBasedMultiset.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; +import static org.elasticsearch.util.gcommon.collect.Multisets.checkNonnegative; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +/** + * Basic implementation of {@code Multiset} backed by an instance of {@code + * Map}. + * + *

For serialization to work, the subclass must specify explicit {@code + * readObject} and {@code writeObject} methods. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +abstract class AbstractMapBasedMultiset extends AbstractMultiset + implements Serializable { + + // TODO: Replace AtomicInteger with a to-be-written IntegerHolder class for + // better performance. + private transient Map backingMap; + + /* + * Cache the size for efficiency. Using a long lets us avoid the need for + * overflow checking and ensures that size() will function correctly even if + * the multiset had once been larger than Integer.MAX_VALUE. + */ + private transient long size; + + /** Standard constructor. */ + protected AbstractMapBasedMultiset(Map backingMap) { + this.backingMap = checkNotNull(backingMap); + this.size = super.size(); + } + + Map backingMap() { + return backingMap; + } + + /** Used during deserialization only. The backing map must be empty. */ + void setBackingMap(Map backingMap) { + this.backingMap = backingMap; + } + + // Required Implementations + + private transient EntrySet entrySet; + + /** + * {@inheritDoc} + * + *

Invoking {@link Multiset.Entry#getCount} on an entry in the returned + * set always returns the current count of that element in the multiset, as + * opposed to the count at the time the entry was retrieved. + */ + @Override public Set> entrySet() { + EntrySet result = entrySet; + if (result == null) { + entrySet = result = new EntrySet(); + } + return result; + } + + private class EntrySet extends AbstractSet> { + @Override public Iterator> iterator() { + final Iterator> backingEntries + = backingMap.entrySet().iterator(); + return new Iterator>() { + Map.Entry toRemove; + + public boolean hasNext() { + return backingEntries.hasNext(); + } + + public Multiset.Entry next() { + final Map.Entry mapEntry = backingEntries.next(); + toRemove = mapEntry; + return new Multisets.AbstractEntry() { + public E getElement() { + return mapEntry.getKey(); + } + public int getCount() { + int count = mapEntry.getValue().get(); + if (count == 0) { + AtomicInteger frequency = backingMap.get(getElement()); + if (frequency != null) { + count = frequency.get(); + } + } + return count; + } + }; + } + + public void remove() { + checkState(toRemove != null, + "no calls to next() since the last call to remove()"); + size -= toRemove.getValue().getAndSet(0); + backingEntries.remove(); + toRemove = null; + } + }; + } + + @Override public int size() { + return backingMap.size(); + } + + // The following overrides are for better performance. + + @Override public void clear() { + for (AtomicInteger frequency : backingMap.values()) { + frequency.set(0); + } + backingMap.clear(); + size = 0L; + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + int count = count(entry.getElement()); + return (count == entry.getCount()) && (count > 0); + } + return false; + } + + @Override public boolean remove(Object o) { + if (contains(o)) { + Entry entry = (Entry) o; + AtomicInteger frequency = backingMap.remove(entry.getElement()); + int numberRemoved = frequency.getAndSet(0); + size -= numberRemoved; + return true; + } + return false; + } + } + + // Optimizations - Query Operations + + @Override public int size() { + return (int) Math.min(this.size, Integer.MAX_VALUE); + } + + @Override public Iterator iterator() { + return new MapBasedMultisetIterator(); + } + + /* + * Not subclassing AbstractMultiset$MultisetIterator because next() needs to + * retrieve the Map.Entry entry, which can then be used for + * a more efficient remove() call. + */ + private class MapBasedMultisetIterator implements Iterator { + final Iterator> entryIterator; + Map.Entry currentEntry; + int occurrencesLeft; + boolean canRemove; + + MapBasedMultisetIterator() { + this.entryIterator = backingMap.entrySet().iterator(); + } + + public boolean hasNext() { + return occurrencesLeft > 0 || entryIterator.hasNext(); + } + + public E next() { + if (occurrencesLeft == 0) { + currentEntry = entryIterator.next(); + occurrencesLeft = currentEntry.getValue().get(); + } + occurrencesLeft--; + canRemove = true; + return currentEntry.getKey(); + } + + public void remove() { + checkState(canRemove, + "no calls to next() since the last call to remove()"); + int frequency = currentEntry.getValue().get(); + if (frequency <= 0) { + throw new ConcurrentModificationException(); + } + if (currentEntry.getValue().addAndGet(-1) == 0) { + entryIterator.remove(); + } + size--; + canRemove = false; + } + } + + @Override public int count(@Nullable Object element) { + AtomicInteger frequency = backingMap.get(element); + return (frequency == null) ? 0 : frequency.get(); + } + + // Optional Operations - Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the call would result in more than + * {@link Integer#MAX_VALUE} occurrences of {@code element} in this + * multiset. + */ + @Override public int add(@Nullable E element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument( + occurrences > 0, "occurrences cannot be negative: %s", occurrences); + AtomicInteger frequency = backingMap.get(element); + int oldCount; + if (frequency == null) { + oldCount = 0; + backingMap.put(element, new AtomicInteger(occurrences)); + } else { + oldCount = frequency.get(); + long newCount = (long) oldCount + (long) occurrences; + checkArgument(newCount <= Integer.MAX_VALUE, + "too many occurrences: %s", newCount); + frequency.getAndAdd(occurrences); + } + size += occurrences; + return oldCount; + } + + @Override public int remove(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument( + occurrences > 0, "occurrences cannot be negative: %s", occurrences); + AtomicInteger frequency = backingMap.get(element); + if (frequency == null) { + return 0; + } + + int oldCount = frequency.get(); + + int numberRemoved; + if (oldCount > occurrences) { + numberRemoved = occurrences; + } else { + numberRemoved = oldCount; + backingMap.remove(element); + } + + frequency.addAndGet(-numberRemoved); + size -= numberRemoved; + return oldCount; + } + + // Roughly a 33% performance improvement over AbstractMultiset.setCount(). + @Override public int setCount(E element, int count) { + checkNonnegative(count, "count"); + + AtomicInteger existingCounter; + int oldCount; + if (count == 0) { + existingCounter = backingMap.remove(element); + oldCount = getAndSet(existingCounter, count); + } else { + existingCounter = backingMap.get(element); + oldCount = getAndSet(existingCounter, count); + + if (existingCounter == null) { + backingMap.put(element, new AtomicInteger(count)); + } + } + + size += (count - oldCount); + return oldCount; + } + + private static int getAndSet(AtomicInteger i, int count) { + if (i == null) { + return 0; + } + + return i.getAndSet(count); + } + + private int removeAllOccurrences(@Nullable Object element, + Map map) { + AtomicInteger frequency = map.remove(element); + if (frequency == null) { + return 0; + } + int numberRemoved = frequency.getAndSet(0); + size -= numberRemoved; + return numberRemoved; + } + + // Views + + @Override Set createElementSet() { + return new MapBasedElementSet(backingMap); + } + + class MapBasedElementSet extends ForwardingSet { + + // This mapping is the usually the same as {@code backingMap}, but can be a + // submap in some implementations. + private final Map map; + private final Set delegate; + + MapBasedElementSet(Map map) { + this.map = map; + delegate = map.keySet(); + } + + @Override protected Set delegate() { + return delegate; + } + + // TODO: a way to not have to write this much code? + + @Override public Iterator iterator() { + final Iterator> entries + = map.entrySet().iterator(); + return new Iterator() { + Map.Entry toRemove; + + public boolean hasNext() { + return entries.hasNext(); + } + + public E next() { + toRemove = entries.next(); + return toRemove.getKey(); + } + + public void remove() { + checkState(toRemove != null, + "no calls to next() since the last call to remove()"); + size -= toRemove.getValue().getAndSet(0); + entries.remove(); + toRemove = null; + } + }; + } + + @Override public boolean remove(Object element) { + return removeAllOccurrences(element, map) != 0; + } + + @Override public boolean removeAll(Collection elementsToRemove) { + return Iterators.removeAll(iterator(), elementsToRemove); + } + + @Override public boolean retainAll(Collection elementsToRetain) { + return Iterators.retainAll(iterator(), elementsToRetain); + } + + @Override public void clear() { + if (map == backingMap) { + AbstractMapBasedMultiset.this.clear(); + } else { + Iterator i = iterator(); + while (i.hasNext()) { + i.next(); + i.remove(); + } + } + } + + public Map getMap() { + return map; + } + } + + // Don't allow default serialization. + @SuppressWarnings("unused") // actually used during deserialization + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Stream data required"); + } + + private static final long serialVersionUID = -2250766705698539974L; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMapEntry.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMapEntry.java new file mode 100644 index 00000000000..751f608f1c8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMapEntry.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Objects; + +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +/** + * Implementation of the {@code equals}, {@code hashCode}, and {@code toString} + * methods of {@code Entry}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractMapEntry implements Entry { + + public abstract K getKey(); + + public abstract V getValue(); + + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Entry) { + Entry that = (Entry) object; + return Objects.equal(this.getKey(), that.getKey()) + && Objects.equal(this.getValue(), that.getValue()); + } + return false; + } + + @Override public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + /** + * Returns a string representation of the form {key}={value}. + */ + @Override public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMultimap.java new file mode 100644 index 00000000000..d3745550a83 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMultimap.java @@ -0,0 +1,1492 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link Multimap} interface. This class represents + * a multimap as a map that associates each key with a collection of values. All + * methods of {@link Multimap} are supported, including those specified as + * optional in the interface. + * + *

To implement a multimap, a subclass must define the method {@link + * #createCollection()}, which creates an empty collection of values for a key. + * + *

The multimap constructor takes a map that has a single entry for each + * distinct key. When you insert a key-value pair with a key that isn't already + * in the multimap, {@code AbstractMultimap} calls {@link #createCollection()} + * to create the collection of values for that key. The subclass should not call + * {@link #createCollection()} directly, and a new instance should be created + * every time the method is called. + * + *

For example, the subclass could pass a {@link java.util.TreeMap} during + * construction, and {@link #createCollection()} could return a {@link + * java.util.TreeSet}, in which case the multimap's iterators would propagate + * through the keys and values in sorted order. + * + *

Keys and values may be null, as long as the underlying collection classes + * support null elements. + * + *

The collections created by {@link #createCollection()} may or may not + * allow duplicates. If the collection, such as a {@link Set}, does not support + * duplicates, an added key-value pair will replace an existing pair with the + * same key and value, if such a pair is present. With collections like {@link + * List} that allow duplicates, the collection will keep the existing key-value + * pairs while adding a new pair. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap, even if the underlying map and {@link #createCollection()} method + * return threadsafe classes. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedMultimap}. + * + *

For serialization to work, the subclass must specify explicit + * {@code readObject} and {@code writeObject} methods. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractMultimap implements Multimap, Serializable { + /* + * Here's an outline of the overall design. + * + * The map variable contains the collection of values associated with each + * key. When a key-value pair is added to a multimap that didn't previously + * contain any values for that key, a new collection generated by + * createCollection is added to the map. That same collection instance + * remains in the map as long as the multimap has any values for the key. If + * all values for the key are removed, the key and collection are removed + * from the map. + * + * The get method returns a WrappedCollection, which decorates the collection + * in the map (if the key is present) or an empty collection (if the key is + * not present). When the collection delegate in the WrappedCollection is + * empty, the multimap may contain subsequently added values for that key. To + * handle that situation, the WrappedCollection checks whether map contains + * an entry for the provided key, and if so replaces the delegate. + */ + + private transient Map> map; + private transient int totalSize; + + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @throws IllegalArgumentException if {@code map} is not empty + */ + protected AbstractMultimap(Map> map) { + checkArgument(map.isEmpty()); + this.map = map; + } + + /** Used during deserialization only. */ + final void setMap(Map> map) { + this.map = map; + totalSize = 0; + for (Collection values : map.values()) { + checkArgument(!values.isEmpty()); + totalSize += values.size(); + } + } + + /** + * Creates the collection of values for a single key. + * + *

Collections with weak, soft, or phantom references are not supported. + * Each call to {@code createCollection} should create a new instance. + * + *

The returned collection class determines whether duplicate key-value + * pairs are allowed. + * + * @return an empty collection of values + */ + abstract Collection createCollection(); + + /** + * Creates the collection of values for an explicitly provided key. By + * default, it simply calls {@link #createCollection()}, which is the correct + * behavior for most implementations. The {@link LinkedHashMultimap} class + * overrides it. + * + * @param key key to associate with values in the collection + * @return an empty collection of values + */ + Collection createCollection(@Nullable K key) { + return createCollection(); + } + + Map> backingMap() { + return map; + } + + // Query Operations + + public int size() { + return totalSize; + } + + public boolean isEmpty() { + return totalSize == 0; + } + + public boolean containsKey(@Nullable Object key) { + return map.containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + for (Collection collection : map.values()) { + if (collection.contains(value)) { + return true; + } + } + + return false; + } + + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + Collection collection = map.get(key); + return collection != null && collection.contains(value); + } + + // Modification Operations + + public boolean put(@Nullable K key, @Nullable V value) { + Collection collection = getOrCreateCollection(key); + + if (collection.add(value)) { + totalSize++; + return true; + } else { + return false; + } + } + + private Collection getOrCreateCollection(@Nullable K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + map.put(key, collection); + } + return collection; + } + + public boolean remove(@Nullable Object key, @Nullable Object value) { + Collection collection = map.get(key); + if (collection == null) { + return false; + } + + boolean changed = collection.remove(value); + if (changed) { + totalSize--; + if (collection.isEmpty()) { + map.remove(key); + } + } + return changed; + } + + // Bulk Operations + + public boolean putAll(@Nullable K key, Iterable values) { + if (!values.iterator().hasNext()) { + return false; + } + Collection collection = getOrCreateCollection(key); + int oldSize = collection.size(); + + boolean changed = false; + if (values instanceof Collection) { + @SuppressWarnings("unchecked") + Collection c = (Collection) values; + changed = collection.addAll(c); + } else { + for (V value : values) { + changed |= collection.add(value); + } + } + + totalSize += (collection.size() - oldSize); + return changed; + } + + public boolean putAll(Multimap multimap) { + boolean changed = false; + for (Map.Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + public Collection replaceValues( + @Nullable K key, Iterable values) { + Iterator iterator = values.iterator(); + if (!iterator.hasNext()) { + return removeAll(key); + } + + Collection collection = getOrCreateCollection(key); + Collection oldValues = createCollection(); + oldValues.addAll(collection); + + totalSize -= collection.size(); + collection.clear(); + + while (iterator.hasNext()) { + if (collection.add(iterator.next())) { + totalSize++; + } + } + + return unmodifiableCollectionSubclass(oldValues); + } + + /** + * {@inheritDoc} + * + *

The returned collection is immutable. + */ + public Collection removeAll(@Nullable Object key) { + Collection collection = map.remove(key); + Collection output = createCollection(); + + if (collection != null) { + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + } + + return unmodifiableCollectionSubclass(output); + } + + private Collection unmodifiableCollectionSubclass( + Collection collection) { + if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } else { + return Collections.unmodifiableCollection(collection); + } + } + + public void clear() { + // Clear each collection, to make previously returned collections empty. + for (Collection collection : map.values()) { + collection.clear(); + } + map.clear(); + totalSize = 0; + } + + // Views + + /** + * {@inheritDoc} + * + *

The returned collection is not serializable. + */ + public Collection get(@Nullable K key) { + Collection collection = map.get(key); + if (collection == null) { + collection = createCollection(key); + } + return wrapCollection(key, collection); + } + + /** + * Generates a decorated collection that remains consistent with the values in + * the multimap for the provided key. Changes to the multimap may alter the + * returned collection, and vice versa. + */ + private Collection wrapCollection( + @Nullable K key, Collection collection) { + if (collection instanceof SortedSet) { + return new WrappedSortedSet(key, (SortedSet) collection, null); + } else if (collection instanceof Set) { + return new WrappedSet(key, (Set) collection); + } else if (collection instanceof List) { + return wrapList(key, (List) collection, null); + } else { + return new WrappedCollection(key, collection, null); + } + } + + private List wrapList( + K key, List list, @Nullable WrappedCollection ancestor) { + return (list instanceof RandomAccess) + ? new RandomAccessWrappedList(key, list, ancestor) + : new WrappedList(key, list, ancestor); + } + + /** + * Collection decorator that stays in sync with the multimap values for a key. + * There are two kinds of wrapped collections: full and subcollections. Both + * have a delegate pointing to the underlying collection class. + * + *

Full collections, identified by a null ancestor field, contain all + * multimap values for a given key. Its delegate is a value in {@link + * AbstractMultimap#map} whenever the delegate is non-empty. The {@code + * refreshIfEmpty}, {@code removeIfEmpty}, and {@code addToMap} methods ensure + * that the {@code WrappedCollection} and map remain consistent. + * + *

A subcollection, such as a sublist, contains some of the values for a + * given key. Its ancestor field points to the full wrapped collection with + * all values for the key. The subcollection {@code refreshIfEmpty}, {@code + * removeIfEmpty}, and {@code addToMap} methods call the corresponding methods + * of the full wrapped collection. + */ + private class WrappedCollection extends AbstractCollection { + final K key; + Collection delegate; + final WrappedCollection ancestor; + final Collection ancestorDelegate; + + WrappedCollection(@Nullable K key, Collection delegate, + @Nullable WrappedCollection ancestor) { + this.key = key; + this.delegate = delegate; + this.ancestor = ancestor; + this.ancestorDelegate + = (ancestor == null) ? null : ancestor.getDelegate(); + } + + /** + * If the delegate collection is empty, but the multimap has values for the + * key, replace the delegate with the new collection for the key. + * + *

For a subcollection, refresh its ancestor and validate that the + * ancestor delegate hasn't changed. + */ + void refreshIfEmpty() { + if (ancestor != null) { + ancestor.refreshIfEmpty(); + if (ancestor.getDelegate() != ancestorDelegate) { + throw new ConcurrentModificationException(); + } + } else if (delegate.isEmpty()) { + Collection newDelegate = map.get(key); + if (newDelegate != null) { + delegate = newDelegate; + } + } + } + + /** + * If collection is empty, remove it from {@code map}. For subcollections, + * check whether the ancestor collection is empty. + */ + void removeIfEmpty() { + if (ancestor != null) { + ancestor.removeIfEmpty(); + } else if (delegate.isEmpty()) { + map.remove(key); + } + } + + K getKey() { + return key; + } + + /** + * Add the delegate to the map. Other {@code WrappedCollection} methods + * should call this method after adding elements to a previously empty + * collection. + * + *

Subcollection add the ancestor's delegate instead. + */ + void addToMap() { + if (ancestor != null) { + ancestor.addToMap(); + } else { + map.put(key, delegate); + } + } + + @Override public int size() { + refreshIfEmpty(); + return delegate.size(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + refreshIfEmpty(); + return delegate.equals(object); + } + + @Override public int hashCode() { + refreshIfEmpty(); + return delegate.hashCode(); + } + + @Override public String toString() { + refreshIfEmpty(); + return delegate.toString(); + } + + Collection getDelegate() { + return delegate; + } + + @Override public Iterator iterator() { + refreshIfEmpty(); + return new WrappedIterator(); + } + + /** Collection iterator for {@code WrappedCollection}. */ + class WrappedIterator implements Iterator { + final Iterator delegateIterator; + final Collection originalDelegate = delegate; + + WrappedIterator() { + delegateIterator = iteratorOrListIterator(delegate); + } + + WrappedIterator(Iterator delegateIterator) { + this.delegateIterator = delegateIterator; + } + + /** + * If the delegate changed since the iterator was created, the iterator is + * no longer valid. + */ + void validateIterator() { + refreshIfEmpty(); + if (delegate != originalDelegate) { + throw new ConcurrentModificationException(); + } + } + + public boolean hasNext() { + validateIterator(); + return delegateIterator.hasNext(); + } + + public V next() { + validateIterator(); + return delegateIterator.next(); + } + + public void remove() { + delegateIterator.remove(); + totalSize--; + removeIfEmpty(); + } + + Iterator getDelegateIterator() { + validateIterator(); + return delegateIterator; + } + } + + @Override public boolean add(V value) { + refreshIfEmpty(); + boolean wasEmpty = delegate.isEmpty(); + boolean changed = delegate.add(value); + if (changed) { + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + return changed; + } + + WrappedCollection getAncestor() { + return ancestor; + } + + // The following methods are provided for better performance. + + @Override public boolean addAll(Collection collection) { + if (collection.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.addAll(collection); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + @Override public boolean contains(Object o) { + refreshIfEmpty(); + return delegate.contains(o); + } + + @Override public boolean containsAll(Collection c) { + refreshIfEmpty(); + return delegate.containsAll(c); + } + + @Override public void clear() { + int oldSize = size(); // calls refreshIfEmpty + if (oldSize == 0) { + return; + } + delegate.clear(); + totalSize -= oldSize; + removeIfEmpty(); // maybe shouldn't be removed if this is a sublist + } + + @Override public boolean remove(Object o) { + refreshIfEmpty(); + boolean changed = delegate.remove(o); + if (changed) { + totalSize--; + removeIfEmpty(); + } + return changed; + } + + @Override public boolean removeAll(Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.removeAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + + @Override public boolean retainAll(Collection c) { + checkNotNull(c); + int oldSize = size(); // calls refreshIfEmpty + boolean changed = delegate.retainAll(c); + if (changed) { + int newSize = delegate.size(); + totalSize += (newSize - oldSize); + removeIfEmpty(); + } + return changed; + } + } + + private Iterator iteratorOrListIterator(Collection collection) { + return (collection instanceof List) + ? ((List) collection).listIterator() + : collection.iterator(); + } + + /** Set decorator that stays in sync with the multimap values for a key. */ + private class WrappedSet extends WrappedCollection implements Set { + WrappedSet(K key, Set delegate) { + super(key, delegate, null); + } + } + + /** + * SortedSet decorator that stays in sync with the multimap values for a key. + */ + private class WrappedSortedSet extends WrappedCollection + implements SortedSet { + WrappedSortedSet(@Nullable K key, SortedSet delegate, + @Nullable WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + SortedSet getSortedSetDelegate() { + return (SortedSet) getDelegate(); + } + + public Comparator comparator() { + return getSortedSetDelegate().comparator(); + } + + public V first() { + refreshIfEmpty(); + return getSortedSetDelegate().first(); + } + + public V last() { + refreshIfEmpty(); + return getSortedSetDelegate().last(); + } + + public SortedSet headSet(V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), getSortedSetDelegate().headSet(toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + public SortedSet subSet(V fromElement, V toElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), getSortedSetDelegate().subSet(fromElement, toElement), + (getAncestor() == null) ? this : getAncestor()); + } + + public SortedSet tailSet(V fromElement) { + refreshIfEmpty(); + return new WrappedSortedSet( + getKey(), getSortedSetDelegate().tailSet(fromElement), + (getAncestor() == null) ? this : getAncestor()); + } + } + + /** List decorator that stays in sync with the multimap values for a key. */ + private class WrappedList extends WrappedCollection implements List { + WrappedList(K key, List delegate, @Nullable WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + + List getListDelegate() { + return (List) getDelegate(); + } + + public boolean addAll(int index, Collection c) { + if (c.isEmpty()) { + return false; + } + int oldSize = size(); // calls refreshIfEmpty + boolean changed = getListDelegate().addAll(index, c); + if (changed) { + int newSize = getDelegate().size(); + totalSize += (newSize - oldSize); + if (oldSize == 0) { + addToMap(); + } + } + return changed; + } + + public V get(int index) { + refreshIfEmpty(); + return getListDelegate().get(index); + } + + public V set(int index, V element) { + refreshIfEmpty(); + return getListDelegate().set(index, element); + } + + public void add(int index, V element) { + refreshIfEmpty(); + boolean wasEmpty = getDelegate().isEmpty(); + getListDelegate().add(index, element); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + + public V remove(int index) { + refreshIfEmpty(); + V value = getListDelegate().remove(index); + totalSize--; + removeIfEmpty(); + return value; + } + + public int indexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().indexOf(o); + } + + public int lastIndexOf(Object o) { + refreshIfEmpty(); + return getListDelegate().lastIndexOf(o); + } + + public ListIterator listIterator() { + refreshIfEmpty(); + return new WrappedListIterator(); + } + + public ListIterator listIterator(int index) { + refreshIfEmpty(); + return new WrappedListIterator(index); + } + + @GwtIncompatible("List.subList") + public List subList(int fromIndex, int toIndex) { + refreshIfEmpty(); + return wrapList(getKey(), + Platform.subList(getListDelegate(), fromIndex, toIndex), + (getAncestor() == null) ? this : getAncestor()); + } + + /** ListIterator decorator. */ + private class WrappedListIterator extends WrappedIterator + implements ListIterator { + WrappedListIterator() {} + + public WrappedListIterator(int index) { + super(getListDelegate().listIterator(index)); + } + + private ListIterator getDelegateListIterator() { + return (ListIterator) getDelegateIterator(); + } + + public boolean hasPrevious() { + return getDelegateListIterator().hasPrevious(); + } + + public V previous() { + return getDelegateListIterator().previous(); + } + + public int nextIndex() { + return getDelegateListIterator().nextIndex(); + } + + public int previousIndex() { + return getDelegateListIterator().previousIndex(); + } + + public void set(V value) { + getDelegateListIterator().set(value); + } + + public void add(V value) { + boolean wasEmpty = isEmpty(); + getDelegateListIterator().add(value); + totalSize++; + if (wasEmpty) { + addToMap(); + } + } + } + } + + /** + * List decorator that stays in sync with the multimap values for a key and + * supports rapid random access. + */ + private class RandomAccessWrappedList extends WrappedList + implements RandomAccess { + RandomAccessWrappedList(K key, List delegate, + @Nullable WrappedCollection ancestor) { + super(key, delegate, ancestor); + } + } + + private transient Set keySet; + + public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = createKeySet() : result; + } + + private Set createKeySet() { + return (map instanceof SortedMap) + ? new SortedKeySet((SortedMap>) map) : new KeySet(map); + } + + private class KeySet extends AbstractSet { + + /** + * This is usually the same as map, except when someone requests a + * subcollection of a {@link SortedKeySet}. + */ + final Map> subMap; + + KeySet(final Map> subMap) { + this.subMap = subMap; + } + + @Override public int size() { + return subMap.size(); + } + + @Override public Iterator iterator() { + return new Iterator() { + final Iterator>> entryIterator + = subMap.entrySet().iterator(); + Map.Entry> entry; + + public boolean hasNext() { + return entryIterator.hasNext(); + } + public K next() { + entry = entryIterator.next(); + return entry.getKey(); + } + public void remove() { + checkState(entry != null); + Collection collection = entry.getValue(); + entryIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + } + }; + } + + // The following methods are included for better performance. + + @Override public boolean contains(Object key) { + return subMap.containsKey(key); + } + + @Override public boolean remove(Object key) { + int count = 0; + Collection collection = subMap.remove(key); + if (collection != null) { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count > 0; + } + + @Override public boolean containsAll(Collection c) { + return subMap.keySet().containsAll(c); + } + + @Override public boolean equals(@Nullable Object object) { + return this == object || this.subMap.keySet().equals(object); + } + + @Override public int hashCode() { + return subMap.keySet().hashCode(); + } + } + + private class SortedKeySet extends KeySet implements SortedSet { + + SortedKeySet(SortedMap> subMap) { + super(subMap); + } + + SortedMap> sortedMap() { + return (SortedMap>) subMap; + } + + public Comparator comparator() { + return sortedMap().comparator(); + } + + public K first() { + return sortedMap().firstKey(); + } + + public SortedSet headSet(K toElement) { + return new SortedKeySet(sortedMap().headMap(toElement)); + } + + public K last() { + return sortedMap().lastKey(); + } + + public SortedSet subSet(K fromElement, K toElement) { + return new SortedKeySet(sortedMap().subMap(fromElement, toElement)); + } + + public SortedSet tailSet(K fromElement) { + return new SortedKeySet(sortedMap().tailMap(fromElement)); + } + } + + private transient Multiset multiset; + + public Multiset keys() { + Multiset result = multiset; + return (result == null) ? multiset = new MultisetView() : result; + } + + /** Multiset view that stays in sync with the multimap keys. */ + private class MultisetView extends AbstractMultiset { + + @Override public int remove(Object key, int occurrences) { + if (occurrences == 0) { + return count(key); + } + checkArgument(occurrences > 0); + + Collection collection; + try { + collection = map.get(key); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + + if (collection == null) { + return 0; + } + int count = collection.size(); + + if (occurrences >= count) { + return removeValuesForKey(key); + } + + Iterator iterator = collection.iterator(); + for (int i = 0; i < occurrences; i++) { + iterator.next(); + iterator.remove(); + } + totalSize -= occurrences; + return count; + } + + @Override public Set elementSet() { + return AbstractMultimap.this.keySet(); + } + + transient Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + private class EntrySet extends AbstractSet> { + @Override public Iterator> iterator() { + return new MultisetEntryIterator(); + } + @Override public int size() { + return map.size(); + } + + // The following methods are included for better performance. + + @Override public boolean contains(Object o) { + if (!(o instanceof Multiset.Entry)) { + return false; + } + Multiset.Entry entry = (Multiset.Entry) o; + Collection collection = map.get(entry.getElement()); + return (collection != null) && + (collection.size() == entry.getCount()); + } + @Override public void clear() { + AbstractMultimap.this.clear(); + } + @Override public boolean remove(Object o) { + return contains(o) && + (removeValuesForKey(((Multiset.Entry) o).getElement()) > 0); + } + } + + @Override public Iterator iterator() { + return new MultisetKeyIterator(); + } + + // The following methods are included for better performance. + + @Override public int count(Object key) { + try { + Collection collection = map.get(key); + return (collection == null) ? 0 : collection.size(); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + @Override public int size() { + return totalSize; + } + + @Override public void clear() { + AbstractMultimap.this.clear(); + } + } + + /** + * Removes all values for the provided key. Unlike {@link #removeAll}, it + * returns the number of removed mappings. + */ + private int removeValuesForKey(Object key) { + Collection collection; + try { + collection = map.remove(key); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + + int count = 0; + if (collection != null) { + count = collection.size(); + collection.clear(); + totalSize -= count; + } + return count; + } + + /** Iterator across each key, repeating once per value. */ + private class MultisetEntryIterator implements Iterator> { + final Iterator>> asMapIterator + = asMap().entrySet().iterator(); + + public boolean hasNext() { + return asMapIterator.hasNext(); + } + public Multiset.Entry next() { + return new MultisetEntry(asMapIterator.next()); + } + public void remove() { + asMapIterator.remove(); + } + } + + private class MultisetEntry extends Multisets.AbstractEntry { + final Map.Entry> entry; + + public MultisetEntry(Map.Entry> entry) { + this.entry = entry; + } + public K getElement() { + return entry.getKey(); + } + public int getCount() { + return entry.getValue().size(); + } + } + + /** Iterator across each key, repeating once per value. */ + private class MultisetKeyIterator implements Iterator { + final Iterator> entryIterator = entries().iterator(); + + public boolean hasNext() { + return entryIterator.hasNext(); + } + public K next() { + return entryIterator.next().getKey(); + } + public void remove() { + entryIterator.remove(); + } + } + + private transient Collection valuesCollection; + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values + * for one key, followed by the values of a second key, and so on. + */ + public Collection values() { + Collection result = valuesCollection; + return (result == null) ? valuesCollection = new Values() : result; + } + + private class Values extends AbstractCollection { + @Override public Iterator iterator() { + return new ValueIterator(); + } + @Override public int size() { + return totalSize; + } + + // The following methods are included to improve performance. + + @Override public void clear() { + AbstractMultimap.this.clear(); + } + + @Override public boolean contains(Object value) { + return containsValue(value); + } + } + + /** Iterator across all values. */ + private class ValueIterator implements Iterator { + final Iterator> entryIterator = createEntryIterator(); + + public boolean hasNext() { + return entryIterator.hasNext(); + } + public V next() { + return entryIterator.next().getValue(); + } + public void remove() { + entryIterator.remove(); + } + } + + private transient Collection> entries; + + // TODO: should we copy this javadoc to each concrete class, so that classes + // like LinkedHashMultimap that need to say something different are still + // able to {@inheritDoc} all the way from Multimap? + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values + * for one key, followed by the values of a second key, and so on. + * + *

Each entry is an immutable snapshot of a key-value mapping in the + * multimap, taken at the time the entry is returned by a method call to the + * collection or its iterator. + */ + public Collection> entries() { + Collection> result = entries; + return (entries == null) ? entries = createEntries() : result; + } + + private Collection> createEntries() { + // TODO: can we refactor so we're not doing "this instanceof"? + return (this instanceof SetMultimap) ? new EntrySet() : new Entries(); + } + + /** Entries for multimap. */ + private class Entries extends AbstractCollection> { + @Override public Iterator> iterator() { + return createEntryIterator(); + } + @Override public int size() { + return totalSize; + } + + // The following methods are included to improve performance. + + @Override public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + return containsEntry(entry.getKey(), entry.getValue()); + } + + @Override public void clear() { + AbstractMultimap.this.clear(); + } + + @Override public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + return AbstractMultimap.this.remove(entry.getKey(), entry.getValue()); + } + } + + /** + * Returns an iterator across all key-value map entries, used by {@code + * entries().iterator()} and {@code values().iterator()}. The default + * behavior, which traverses the values for one key, the values for a second + * key, and so on, suffices for most {@code AbstractMultimap} implementations. + * + * @return an iterator across map entries + */ + Iterator> createEntryIterator() { + return new EntryIterator(); + } + + /** Iterator across all key-value pairs. */ + private class EntryIterator implements Iterator> { + final Iterator>> keyIterator; + K key; + Collection collection; + Iterator valueIterator; + + EntryIterator() { + keyIterator = map.entrySet().iterator(); + if (keyIterator.hasNext()) { + findValueIteratorAndKey(); + } else { + valueIterator = Iterators.emptyModifiableIterator(); + } + } + + void findValueIteratorAndKey() { + Map.Entry> entry = keyIterator.next(); + key = entry.getKey(); + collection = entry.getValue(); + valueIterator = collection.iterator(); + } + + public boolean hasNext() { + return keyIterator.hasNext() || valueIterator.hasNext(); + } + + public Map.Entry next() { + if (!valueIterator.hasNext()) { + findValueIteratorAndKey(); + } + return Maps.immutableEntry(key, valueIterator.next()); + } + + public void remove() { + valueIterator.remove(); + if (collection.isEmpty()) { + keyIterator.remove(); + } + totalSize--; + } + } + + /** Entry set for a {@link SetMultimap}. */ + private class EntrySet extends Entries implements Set> { + @Override public boolean equals(@Nullable Object object) { + return Collections2.setEquals(this, object); + } + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + private transient Map> asMap; + + public Map> asMap() { + Map> result = asMap; + return (result == null) ? asMap = createAsMap() : result; + } + + private Map> createAsMap() { + return (map instanceof SortedMap) + ? new SortedAsMap((SortedMap>) map) : new AsMap(map); + } + + private class AsMap extends AbstractMap> { + /** + * Usually the same as map, but smaller for the headMap(), tailMap(), or + * subMap() of a SortedAsMap. + */ + final transient Map> submap; + + AsMap(Map> submap) { + this.submap = submap; + } + + transient Set>> entrySet; + + @Override public Set>> entrySet() { + Set>> result = entrySet; + return (entrySet == null) ? entrySet = new AsMapEntries() : result; + } + + // The following methods are included for performance. + + @Override public boolean containsKey(Object key) { + return submap.containsKey(key); + } + + @Override public Collection get(Object key) { + Collection collection = submap.get(key); + if (collection == null) { + return null; + } + @SuppressWarnings("unchecked") + K k = (K) key; + return wrapCollection(k, collection); + } + + @Override public Set keySet() { + return AbstractMultimap.this.keySet(); + } + + @Override public Collection remove(Object key) { + Collection collection = submap.remove(key); + if (collection == null) { + return null; + } + + Collection output = createCollection(); + output.addAll(collection); + totalSize -= collection.size(); + collection.clear(); + return output; + } + + @Override public boolean equals(@Nullable Object object) { + return this == object || submap.equals(object); + } + + @Override public int hashCode() { + return submap.hashCode(); + } + + @Override public String toString() { + return submap.toString(); + } + + class AsMapEntries extends AbstractSet>> { + @Override public Iterator>> iterator() { + return new AsMapIterator(); + } + + @Override public int size() { + return submap.size(); + } + + // The following methods are included for performance. + + @Override public boolean contains(Object o) { + return submap.entrySet().contains(o); + } + + @Override public boolean remove(Object o) { + if (!contains(o)) { + return false; + } + Map.Entry entry = (Map.Entry) o; + removeValuesForKey(entry.getKey()); + return true; + } + } + + /** Iterator across all keys and value collections. */ + class AsMapIterator implements Iterator>> { + final Iterator>> delegateIterator + = submap.entrySet().iterator(); + Collection collection; + + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + public Map.Entry> next() { + Map.Entry> entry = delegateIterator.next(); + K key = entry.getKey(); + collection = entry.getValue(); + return Maps.immutableEntry(key, wrapCollection(key, collection)); + } + + public void remove() { + delegateIterator.remove(); + totalSize -= collection.size(); + collection.clear(); + } + } + } + + private class SortedAsMap extends AsMap + implements SortedMap> { + SortedAsMap(SortedMap> submap) { + super(submap); + } + + SortedMap> sortedMap() { + return (SortedMap>) submap; + } + + public Comparator comparator() { + return sortedMap().comparator(); + } + + public K firstKey() { + return sortedMap().firstKey(); + } + + public K lastKey() { + return sortedMap().lastKey(); + } + + public SortedMap> headMap(K toKey) { + return new SortedAsMap(sortedMap().headMap(toKey)); + } + + public SortedMap> subMap(K fromKey, K toKey) { + return new SortedAsMap(sortedMap().subMap(fromKey, toKey)); + } + + public SortedMap> tailMap(K fromKey) { + return new SortedAsMap(sortedMap().tailMap(fromKey)); + } + + SortedSet sortedKeySet; + + // returns a SortedSet, even though returning a Set would be sufficient to + // satisfy the SortedMap.keySet() interface + @Override public SortedSet keySet() { + SortedSet result = sortedKeySet; + return (result == null) + ? sortedKeySet = new SortedKeySet(sortedMap()) : result; + } + } + + // Comparison and hashing + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return this.map.equals(that.asMap()); + } + return false; + } + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + * + * @see Map#hashCode + */ + @Override public int hashCode() { + return map.hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling + * {@code toString} on the map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override public String toString() { + return map.toString(); + } + + private static final long serialVersionUID = 2447537837011683357L; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMultiset.java new file mode 100644 index 00000000000..c4cfb565844 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractMultiset.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Objects; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; +import static org.elasticsearch.util.gcommon.collect.Multisets.setCountImpl; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * This class provides a skeletal implementation of the {@link Multiset} + * interface. A new multiset implementation can be created easily by extending + * this class and implementing the {@link Multiset#entrySet()} method, plus + * optionally overriding {@link #add(Object, int)} and + * {@link #remove(Object, int)} to enable modifications to the multiset. + * + *

The {@link #contains}, {@link #containsAll}, {@link #count}, and + * {@link #size} implementations all iterate across the set returned by + * {@link Multiset#entrySet()}, as do many methods acting on the set returned by + * {@link #elementSet()}. Override those methods for better performance. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +abstract class AbstractMultiset extends AbstractCollection + implements Multiset { + public abstract Set> entrySet(); + + // Query Operations + + @Override public int size() { + long sum = 0L; + for (Entry entry : entrySet()) { + sum += entry.getCount(); + } + return (int) Math.min(sum, Integer.MAX_VALUE); + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public boolean contains(@Nullable Object element) { + return elementSet().contains(element); + } + + @Override public Iterator iterator() { + return new MultisetIterator(); + } + + private class MultisetIterator implements Iterator { + private final Iterator> entryIterator; + private Entry currentEntry; + /** Count of subsequent elements equal to current element */ + private int laterCount; + /** Count of all elements equal to current element */ + private int totalCount; + private boolean canRemove; + + MultisetIterator() { + this.entryIterator = entrySet().iterator(); + } + + public boolean hasNext() { + return laterCount > 0 || entryIterator.hasNext(); + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (laterCount == 0) { + currentEntry = entryIterator.next(); + totalCount = laterCount = currentEntry.getCount(); + } + laterCount--; + canRemove = true; + return currentEntry.getElement(); + } + + public void remove() { + checkState(canRemove, + "no calls to next() since the last call to remove()"); + if (totalCount == 1) { + entryIterator.remove(); + } else { + AbstractMultiset.this.remove(currentEntry.getElement()); + } + totalCount--; + canRemove = false; + } + } + + public int count(Object element) { + for (Entry entry : entrySet()) { + if (Objects.equal(entry.getElement(), element)) { + return entry.getCount(); + } + } + return 0; + } + + // Modification Operations + + @Override public boolean add(@Nullable E element) { + add(element, 1); + return true; + } + + public int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object element) { + return remove(element, 1) > 0; + } + + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + public int setCount(E element, int count) { + return setCountImpl(this, element, count); + } + + public boolean setCount(E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + // Bulk Operations + + @Override public boolean containsAll(Collection elements) { + return elementSet().containsAll(elements); + } + + @Override public boolean addAll(Collection elementsToAdd) { + if (elementsToAdd.isEmpty()) { + return false; + } + if (elementsToAdd instanceof Multiset) { + @SuppressWarnings("unchecked") + Multiset that = (Multiset) elementsToAdd; + for (Entry entry : that.entrySet()) { + add(entry.getElement(), entry.getCount()); + } + } else { + super.addAll(elementsToAdd); + } + return true; + } + + @Override public boolean removeAll(Collection elementsToRemove) { + Collection collection = (elementsToRemove instanceof Multiset) + ? ((Multiset) elementsToRemove).elementSet() : elementsToRemove; + + return elementSet().removeAll(collection); + // TODO: implement retainAll similarly? + } + + @Override public boolean retainAll(Collection elementsToRetain) { + checkNotNull(elementsToRetain); + Iterator> entries = entrySet().iterator(); + boolean modified = false; + while (entries.hasNext()) { + Entry entry = entries.next(); + if (!elementsToRetain.contains(entry.getElement())) { + entries.remove(); + modified = true; + } + } + return modified; + } + + @Override public void clear() { + entrySet().clear(); + } + + // Views + + private transient Set elementSet; + + public Set elementSet() { + Set result = elementSet; + if (result == null) { + elementSet = result = createElementSet(); + } + return result; + } + + /** + * Creates a new instance of this multiset's element set, which will be + * returned by {@link #elementSet()}. + */ + Set createElementSet() { + return new ElementSet(); + } + + private class ElementSet extends AbstractSet { + @Override public Iterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new Iterator() { + public boolean hasNext() { + return entryIterator.hasNext(); + } + public E next() { + return entryIterator.next().getElement(); + } + public void remove() { + entryIterator.remove(); + } + }; + } + @Override public int size() { + return entrySet().size(); + } + } + + // Object methods + + /** + * {@inheritDoc} + * + *

This implementation returns {@code true} if {@code other} is a multiset + * of the same size and if, for each element, the two multisets have the same + * count. + */ + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + /* + * We can't simply check whether the entry sets are equal, since that + * approach fails when a TreeMultiset has a comparator that returns 0 + * when passed unequal elements. + */ + + if (this.size() != that.size()) { + return false; + } + for (Entry entry : that.entrySet()) { + if (count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + * + *

This implementation returns the hash code of {@link + * Multiset#entrySet()}. + */ + @Override public int hashCode() { + return entrySet().hashCode(); + } + + /** + * {@inheritDoc} + * + *

This implementation returns the result of invoking {@code toString} on + * {@link Multiset#entrySet()}. + */ + @Override public String toString() { + return entrySet().toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractSetMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractSetMultimap.java new file mode 100644 index 00000000000..671120d0202 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractSetMultimap.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link SetMultimap} interface. It's a wrapper + * around {@link AbstractMultimap} that converts the returned collections into + * {@code Sets}. The {@link #createCollection} method must return a {@code Set}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractSetMultimap + extends AbstractMultimap implements SetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractSetMultimap(Map> map) { + super(map); + } + + @Override abstract Set createCollection(); + + @Override public Set get(@Nullable K key) { + return (Set) super.get(key); + } + + @Override public Set> entries() { + return (Set>) super.entries(); + } + + @Override public Set removeAll(@Nullable Object key) { + return (Set) super.removeAll(key); + } + + /** + * {@inheritDoc} + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + @Override public Set replaceValues( + @Nullable K key, Iterable values) { + return (Set) super.replaceValues(key, values); + } + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair + */ + @Override public boolean put(K key, V value) { + return super.put(key, value); + } + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they + * contain the same values. Equality does not depend on the ordering of keys + * or values. + */ + @Override public boolean equals(@Nullable Object object) { + return super.equals(object); + } + + private static final long serialVersionUID = 7431625294878419160L; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractSortedSetMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractSortedSetMultimap.java new file mode 100644 index 00000000000..44859ebd293 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AbstractSortedSetMultimap.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Basic implementation of the {@link SortedSetMultimap} interface. It's a + * wrapper around {@link AbstractMultimap} that converts the returned + * collections into sorted sets. The {@link #createCollection} method + * must return a {@code SortedSet}. + * + * @author Jared Levy + */ +@GwtCompatible +abstract class AbstractSortedSetMultimap + extends AbstractSetMultimap implements SortedSetMultimap { + /** + * Creates a new multimap that uses the provided map. + * + * @param map place to store the mapping from each key to its corresponding + * values + */ + protected AbstractSortedSetMultimap(Map> map) { + super(map); + } + + @Override abstract SortedSet createCollection(); + + @Override public SortedSet get(@Nullable K key) { + return (SortedSet) super.get(key); + } + + @Override public SortedSet removeAll(@Nullable Object key) { + return (SortedSet) super.removeAll(key); + } + + @Override public SortedSet replaceValues( + K key, Iterable values) { + return (SortedSet) super.replaceValues(key, values); + } + + /** + * {@inheritDoc} + * + * Consequently, the values do not follow their natural ordering or the + * ordering of the value comparator. + */ + @Override public Collection values() { + return super.values(); + } + + private static final long serialVersionUID = 430848587173315748L; +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ArrayListMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ArrayListMultimap.java new file mode 100644 index 00000000000..a6ca240aa1c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ArrayListMultimap.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@code Multimap} that uses an {@code ArrayList} to store + * the values for a given key. A {@link HashMap} associates each key with an + * {@link ArrayList} of values. + * + *

When iterating through the collections supplied by this class, the + * ordering of values for a given key agrees with the order in which the values + * were added. + * + *

This multimap allows duplicate key-value pairs. After adding a new + * key-value pair equal to an existing key-value pair, the {@code + * ArrayListMultimap} will contain entries for both the new value and the old + * value. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

The lists returned by {@link #get}, {@link #removeAll}, and {@link + * #replaceValues} all implement {@link java.util.RandomAccess}. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedListMultimap}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public final class ArrayListMultimap extends AbstractListMultimap { + // Default from ArrayList + private static final int DEFAULT_VALUES_PER_KEY = 10; + + @VisibleForTesting transient int expectedValuesPerKey; + + /** + * Creates a new, empty {@code ArrayListMultimap} with the default initial + * capacities. + */ + public static ArrayListMultimap create() { + return new ArrayListMultimap(); + } + + /** + * Constructs an empty {@code ArrayListMultimap} with enough capacity to hold + * the specified numbers of keys and values without resizing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code + * expectedValuesPerKey} is negative + */ + public static ArrayListMultimap create( + int expectedKeys, int expectedValuesPerKey) { + return new ArrayListMultimap(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs an {@code ArrayListMultimap} with the same mappings as the + * specified multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static ArrayListMultimap create( + Multimap multimap) { + return new ArrayListMultimap(multimap); + } + + private ArrayListMultimap() { + super(new HashMap>()); + expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + } + + private ArrayListMultimap(int expectedKeys, int expectedValuesPerKey) { + super(Maps.>newHashMapWithExpectedSize(expectedKeys)); + checkArgument(expectedValuesPerKey >= 0); + this.expectedValuesPerKey = expectedValuesPerKey; + } + + private ArrayListMultimap(Multimap multimap) { + this(multimap.keySet().size(), + (multimap instanceof ArrayListMultimap) ? + ((ArrayListMultimap) multimap).expectedValuesPerKey : + DEFAULT_VALUES_PER_KEY); + putAll(multimap); + } + + /** + * Creates a new, empty {@code ArrayList} to hold the collection of values for + * an arbitrary key. + */ + @Override List createCollection() { + return new ArrayList(expectedValuesPerKey); + } + + /** + * Reduces the memory used by this {@code ArrayListMultimap}, if feasible. + */ + public void trimToSize() { + for (Collection collection : backingMap().values()) { + ArrayList arrayList = (ArrayList) collection; + arrayList.trimToSize(); + } + } + + /** + * @serialData expectedValuesPerKey, number of distinct keys, and then for + * each distinct key: the key, number of values for that key, and the + * key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(expectedValuesPerKey); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = stream.readInt(); + int distinctKeys = Serialization.readCount(stream); + Map> map = Maps.newHashMapWithExpectedSize(distinctKeys); + setMap(map); + Serialization.populateMultimap(this, stream, distinctKeys); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AsynchronousComputationException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AsynchronousComputationException.java new file mode 100644 index 00000000000..3a270eb15dc --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/AsynchronousComputationException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +/** + * Wraps an exception that occured during a computation in a different thread. + * + * @author Bob Lee + */ +public class AsynchronousComputationException extends ComputationException { + /** + * Creates a new instance with the given cause. + */ + public AsynchronousComputationException(Throwable cause) { + super(cause); + } + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/BiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/BiMap.java new file mode 100644 index 00000000000..b7011cabc03 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/BiMap.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A bimap (or "bidirectional map") is a map that preserves the uniqueness of + * its values as well as that of its keys. This constraint enables bimaps to + * support an "inverse view", which is another bimap containing the same entries + * as this bimap but with reversed keys and values. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public interface BiMap extends Map { + // Modification Operations + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the given value is already bound to a + * different key in this bimap. The bimap will remain unmodified in this + * event. To avoid this exception, call {@link #forcePut} instead. + */ + V put(@Nullable K key, @Nullable V value); + + /** + * An alternate form of {@code put} that silently removes any existing entry + * with the value {@code value} before proceeding with the {@link #put} + * operation. If the bimap previously contained the provided key-value + * mapping, this method has no effect. + * + *

Note that a successful call to this method could cause the size of the + * bimap to increase by one, stay the same, or even decrease by one. + * + *

Warning: If an existing entry with this value is removed, the key + * for that entry is discarded and not returned. + * + * @param key the key with which the specified value is to be associated + * @param value the value to be associated with the specified key + * @return the value which was previously associated with the key, which may + * be {@code null}, or {@code null} if there was no previous entry + */ + V forcePut(@Nullable K key, @Nullable V value); + + // Bulk Operations + + /** + * {@inheritDoc} + * + *

Warning: the results of calling this method may vary depending on + * the iteration order of {@code map}. + * + * @throws IllegalArgumentException if an attempt to {@code put} any + * entry fails. Note that some map entries may have been added to the + * bimap before the exception was thrown. + */ + void putAll(Map map); + + // Views + + /** + * {@inheritDoc} + * + *

Because a bimap has unique values, this method returns a {@link Set}, + * instead of the {@link java.util.Collection} specified in the {@link Map} + * interface. + */ + Set values(); + + /** + * Returns the inverse view of this bimap, which maps each of this bimap's + * values to its associated key. The two bimaps are backed by the same data; + * any changes to one will appear in the other. + * + *

Note:There is no guaranteed correspondence between the iteration + * order of a bimap and that of its inverse. + * + * @return the inverse view of this bimap + */ + BiMap inverse(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ByFunctionOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ByFunctionOrdering.java new file mode 100644 index 00000000000..f446d888cf6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ByFunctionOrdering.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.base.Objects; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * An ordering that orders elements by applying an order to the result of a + * function on those elements. + */ +@GwtCompatible(serializable = true) +final class ByFunctionOrdering + extends Ordering implements Serializable { + final Function function; + final Ordering ordering; + + ByFunctionOrdering( + Function function, Ordering ordering) { + this.function = checkNotNull(function); + this.ordering = checkNotNull(ordering); + } + + public int compare(F left, F right) { + return ordering.compare(function.apply(left), function.apply(right)); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ByFunctionOrdering) { + ByFunctionOrdering that = (ByFunctionOrdering) object; + return this.function.equals(that.function) + && this.ordering.equals(that.ordering); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(function, ordering); + } + + @Override public String toString() { + return ordering + ".onResultOf(" + function + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ClassToInstanceMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ClassToInstanceMap.java new file mode 100644 index 00000000000..ebe0f4d015d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ClassToInstanceMap.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A map, each entry of which maps a Java + * raw type to an instance of that type. + * In addition to implementing {@code Map}, the additional type-safe operations + * {@link #putInstance} and {@link #getInstance} are available. + * + *

Like any other {@code Map}, this map may contain entries + * for primitive types, and a primitive type and its corresponding wrapper type + * may map to different values. + * + * @param the common supertype that all entries must share; often this is + * simply {@link Object} + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public interface ClassToInstanceMap extends Map, B> { + /** + * Returns the value the specified class is mapped to, or {@code null} if no + * entry for this class is present. This will only return a value that was + * bound to this specific class, not a value that may have been bound to a + * subtype. + */ + T getInstance(Class type); + + /** + * Maps the specified class to the specified value. Does not associate + * this value with any of the class's supertypes. + * + * @return the value previously associated with this class (possibly {@code + * null}), or {@code null} if there was no previous entry. + */ + T putInstance(Class type, @Nullable T value); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Collections2.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Collections2.java new file mode 100644 index 00000000000..682d2c6975d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Collections2.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.base.Joiner; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import org.elasticsearch.util.gcommon.base.Predicate; +import org.elasticsearch.util.gcommon.base.Predicates; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Provides static methods for working with {@code Collection} instances. + * + * @author Chris Povirk + * @author Mike Bostock + * @author Jared Levy + */ +@GwtCompatible +public final class Collections2 { + private Collections2() {} + + /** + * Returns {@code true} if the collection {@code self} contains all of the + * elements in the collection {@code c}. + * + *

This method iterates over the specified collection {@code c}, checking + * each element returned by the iterator in turn to see if it is contained in + * the specified collection {@code self}. If all elements are so contained, + * {@code true} is returned, otherwise {@code false}. + * + * @param self a collection which might contain all elements in {@code c} + * @param c a collection whose elements might be contained by {@code self} + */ + // TODO: Make public? + static boolean containsAll(Collection self, Collection c) { + checkNotNull(self); + for (Object o : c) { + if (!self.contains(o)) { + return false; + } + } + return true; + } + + /** + * Converts an iterable into a collection. If the iterable is already a + * collection, it is returned. Otherwise, an {@link java.util.ArrayList} is + * created with the contents of the iterable in the same iteration order. + */ + static Collection toCollection(Iterable iterable) { + return (iterable instanceof Collection) + ? (Collection) iterable : Lists.newArrayList(iterable); + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * returned collection is a live view of {@code unfiltered}; changes to one + * affect the other. + * + *

The resulting collection's iterator does not support {@code remove()}, + * but all other collection methods are supported. The collection's + * {@code add()} and {@code addAll()} methods throw an + * {@link IllegalArgumentException} if an element that doesn't satisfy the + * predicate is provided. When methods such as {@code removeAll()} and + * {@code clear()} are called on the filtered collection, only elements that + * satisfy the filter will be removed from the underlying collection. + * + *

The returned collection isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered collection's methods, such as {@code size()}, + * iterate across every element in the underlying collection and determine + * which elements satisfy the filter. When a live view is not needed, + * it may be faster to copy {@code Iterables.filter(unfiltered, predicate)} + * and use the copy. + */ + public static Collection filter( + Collection unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredCollection) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + return ((FilteredCollection) unfiltered).createCombined(predicate); + } + + return new FilteredCollection( + checkNotNull(unfiltered), checkNotNull(predicate)); + } + + static class FilteredCollection implements Collection { + final Collection unfiltered; + final Predicate predicate; + + FilteredCollection(Collection unfiltered, + Predicate predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + FilteredCollection createCombined(Predicate newPredicate) { + return new FilteredCollection(unfiltered, + Predicates.and(predicate, newPredicate)); + // . above needed to compile in JDK 5 + } + + public boolean add(E element) { + checkArgument(predicate.apply(element)); + return unfiltered.add(element); + } + + public boolean addAll(Collection collection) { + for (E element : collection) { + checkArgument(predicate.apply(element)); + } + return unfiltered.addAll(collection); + } + + public void clear() { + Iterables.removeIf(unfiltered, predicate); + } + + public boolean contains(Object element) { + try { + // unsafe cast can result in a CCE from predicate.apply(), which we + // will catch + @SuppressWarnings("unchecked") + E e = (E) element; + return predicate.apply(e) && unfiltered.contains(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + public boolean containsAll(Collection collection) { + for (Object element : collection) { + if (!contains(element)) { + return false; + } + } + return true; + } + + public boolean isEmpty() { + return !Iterators.any(unfiltered.iterator(), predicate); + } + + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + + public boolean remove(Object element) { + try { + // unsafe cast can result in a CCE from predicate.apply(), which we + // will catch + @SuppressWarnings("unchecked") + E e = (E) element; + return predicate.apply(e) && unfiltered.remove(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + public boolean removeAll(final Collection collection) { + checkNotNull(collection); + Predicate combinedPredicate = new Predicate() { + public boolean apply(E input) { + return predicate.apply(input) && collection.contains(input); + } + }; + return Iterables.removeIf(unfiltered, combinedPredicate); + } + + public boolean retainAll(final Collection collection) { + checkNotNull(collection); + Predicate combinedPredicate = new Predicate() { + public boolean apply(E input) { + return predicate.apply(input) && !collection.contains(input); + } + }; + return Iterables.removeIf(unfiltered, combinedPredicate); + } + + public int size() { + return Iterators.size(iterator()); + } + + public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + + @Override public String toString() { + return Iterators.toString(iterator()); + } + } + + /** + * Returns a collection that applies {@code function} to each element of + * {@code fromCollection}. The returned collection is a live view of {@code + * fromCollection}; changes to one affect the other. + * + *

The returned collection's {@code add()} and {@code addAll()} methods + * throw an {@link UnsupportedOperationException}. All other collection + * methods are supported, as long as {@code fromCollection} supports them. + * + *

The returned collection isn't threadsafe or serializable, even if + * {@code fromCollection} is. + * + *

When a live view is not needed, it may be faster to copy the + * transformed collection and use the copy. + */ + public static Collection transform(Collection fromCollection, + Function function) { + return new TransformedCollection(fromCollection, function); + } + + static class TransformedCollection extends AbstractCollection { + final Collection fromCollection; + final Function function; + + TransformedCollection(Collection fromCollection, + Function function) { + this.fromCollection = checkNotNull(fromCollection); + this.function = checkNotNull(function); + } + + @Override public void clear() { + fromCollection.clear(); + } + + @Override public boolean isEmpty() { + return fromCollection.isEmpty(); + } + + @Override public Iterator iterator() { + return Iterators.transform(fromCollection.iterator(), function); + } + + @Override public int size() { + return fromCollection.size(); + } + } + + static boolean setEquals(Set thisSet, @Nullable Object object) { + if (object == thisSet) { + return true; + } + if (object instanceof Set) { + Set thatSet = (Set) object; + return thisSet.size() == thatSet.size() + && thisSet.containsAll(thatSet); + } + return false; + } + + static final Joiner standardJoiner = Joiner.on(", "); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ComparatorOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ComparatorOrdering.java new file mode 100644 index 00000000000..33bc5621ca0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ComparatorOrdering.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javax.annotation.Nullable; + +/** An ordering for a pre-existing {@code comparator}. */ +@GwtCompatible(serializable = true) +final class ComparatorOrdering extends Ordering implements Serializable { + final Comparator comparator; + + ComparatorOrdering(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + public int compare(T a, T b) { + return comparator.compare(a, b); + } + + // Override just to remove a level of indirection from inner loops + @Override public int binarySearch(List sortedList, T key) { + return Collections.binarySearch(sortedList, key, comparator); + } + + // Override just to remove a level of indirection from inner loops + @Override public List sortedCopy(Iterable iterable) { + List list = Lists.newArrayList(iterable); + Collections.sort(list, comparator); + return list; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ComparatorOrdering) { + ComparatorOrdering that = (ComparatorOrdering) object; + return this.comparator.equals(that.comparator); + } + return false; + } + + @Override public int hashCode() { + return comparator.hashCode(); + } + + @Override public String toString() { + return comparator.toString(); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/CompoundOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/CompoundOrdering.java new file mode 100644 index 00000000000..3addb9c9978 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/CompoundOrdering.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.List; + +/** An ordering that tries several comparators in order. */ +@GwtCompatible(serializable = true) +final class CompoundOrdering extends Ordering implements Serializable { + final ImmutableList> comparators; + + CompoundOrdering(Comparator primary, + Comparator secondary) { + this.comparators + = ImmutableList.>of(primary, secondary); + } + + CompoundOrdering(Iterable> comparators) { + this.comparators = ImmutableList.copyOf(comparators); + } + + CompoundOrdering(List> comparators, + Comparator lastComparator) { + this.comparators = new ImmutableList.Builder>() + .addAll(comparators).add(lastComparator).build(); + } + + public int compare(T left, T right) { + for (Comparator comparator : comparators) { + int result = comparator.compare(left, right); + if (result != 0) { + return result; + } + } + return 0; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CompoundOrdering) { + CompoundOrdering that = (CompoundOrdering) object; + return this.comparators.equals(that.comparators); + } + return false; + } + + @Override public int hashCode() { + return comparators.hashCode(); + } + + @Override public String toString() { + return "Ordering.compound(" + comparators + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ComputationException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ComputationException.java new file mode 100644 index 00000000000..271d7293f53 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ComputationException.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * Wraps an exception that occured during a computation. + */ +@GwtCompatible +public class ComputationException extends RuntimeException { + /** + * Creates a new instance with the given cause. + */ + public ComputationException(Throwable cause) { + super(cause); + } + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ConcurrentHashMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ConcurrentHashMultiset.java new file mode 100644 index 00000000000..83e7e754e06 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ConcurrentHashMultiset.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.collect.Multisets.checkNonnegative; +import org.elasticsearch.util.gcommon.collect.Serialization.FieldSetter; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.annotation.Nullable; + +/** + * A multiset that supports concurrent modifications and that provides atomic + * versions of most {@code Multiset} operations (exceptions where noted). Null + * elements are not supported. + * + * @author Cliff L. Biffle + */ +public final class ConcurrentHashMultiset extends AbstractMultiset + implements Serializable { + /* + * The ConcurrentHashMultiset's atomic operations are implemented in terms of + * ConcurrentMap's atomic operations. Many of them, such as add(E, int), are + * read-modify-write sequences, and so are implemented as loops that wrap + * ConcurrentMap's compare-and-set operations (like putIfAbsent). + */ + + /** The number of occurrences of each element. */ + private final transient ConcurrentMap countMap; + + // This constant allows the deserialization code to set a final field. This + // holder class makes sure it is not initialized unless an instance is + // deserialized. + private static class FieldSettersHolder { + @SuppressWarnings("unchecked") + // eclipse doesn't like the raw type here, but it's harmless + static final FieldSetter COUNT_MAP_FIELD_SETTER + = Serialization.getFieldSetter( + ConcurrentHashMultiset.class, "countMap"); + } + + /** + * Creates a new, empty {@code ConcurrentHashMultiset} using the default + * initial capacity, load factor, and concurrency settings. + */ + public static ConcurrentHashMultiset create() { + return new ConcurrentHashMultiset(new ConcurrentHashMap()); + } + + /** + * Creates a new {@code ConcurrentHashMultiset} containing the specified + * elements, using the default initial capacity, load factor, and concurrency + * settings. + * + * @param elements the elements that the multiset should contain + */ + public static ConcurrentHashMultiset create( + Iterable elements) { + ConcurrentHashMultiset multiset = ConcurrentHashMultiset.create(); + Iterables.addAll(multiset, elements); + return multiset; + } + + /** + * Creates an instance using {@code countMap} to store elements and their + * counts. + * + *

This instance will assume ownership of {@code countMap}, and other code + * should not maintain references to the map or modify it in any way. + * + * @param countMap backing map for storing the elements in the multiset and + * their counts. It must be empty. + * @throws IllegalArgumentException if {@code countMap} is not empty + */ + @VisibleForTesting ConcurrentHashMultiset( + ConcurrentMap countMap) { + checkArgument(countMap.isEmpty()); + this.countMap = countMap; + } + + // Query Operations + + /** + * Returns the number of occurrences of {@code element} in this multiset. + * + * @param element the element to look for + * @return the nonnegative number of occurrences of the element + */ + @Override public int count(@Nullable Object element) { + try { + return unbox(countMap.get(element)); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + /** + * {@inheritDoc} + * + *

If the data in the multiset is modified by any other threads during this + * method, it is undefined which (if any) of these modifications will be + * reflected in the result. + */ + @Override public int size() { + long sum = 0L; + for (Integer value : countMap.values()) { + sum += value; + } + return (int) Math.min(sum, Integer.MAX_VALUE); + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override public Object[] toArray() { + return snapshot().toArray(); + } + + @Override public T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + /* + * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but + * either of these would recurse back to us again! + */ + private List snapshot() { + List list = Lists.newArrayListWithExpectedSize(size()); + for (Multiset.Entry entry : entrySet()) { + E element = entry.getElement(); + for (int i = entry.getCount(); i > 0; i--) { + list.add(element); + } + } + return list; + } + + // Modification Operations + + /** + * Adds a number of occurrences of the specified element to this multiset. + * + * @param element the element to add + * @param occurrences the number of occurrences to add + * @return the previous count of the element before the operation; possibly + * zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if + * the resulting amount would exceed {@link Integer#MAX_VALUE} + */ + @Override public int add(E element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + int current = count(element); + if (current == 0) { + if (countMap.putIfAbsent(element, occurrences) == null) { + return 0; + } + } else { + checkArgument(occurrences <= Integer.MAX_VALUE - current, + "Overflow adding %s occurrences to a count of %s", + occurrences, current); + int next = current + occurrences; + if (countMap.replace(element, current, next)) { + return current; + } + } + // If we're still here, there was a race, so just try again. + } + } + + /** + * Removes a number of occurrences of the specified element from this + * multiset. If the multiset contains fewer than this number of occurrences to + * begin with, all occurrences will be removed. + * + * @param element the element whose occurrences should be removed + * @param occurrences the number of occurrences of the element to remove + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + @Override public int remove(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + int current = count(element); + if (current == 0) { + return 0; + } + if (occurrences >= current) { + if (countMap.remove(element, current)) { + return current; + } + } else { + // We know it's an "E" because it already exists in the map. + @SuppressWarnings("unchecked") + E casted = (E) element; + + if (countMap.replace(casted, current, current - occurrences)) { + return current; + } + } + // If we're still here, there was a race, so just try again. + } + } + + /** + * Removes all occurrences of the specified element from this multiset. + * This method complements {@link Multiset#remove(Object)}, which removes only + * one occurrence at a time. + * + * @param element the element whose occurrences should all be removed + * @return the number of occurrences successfully removed, possibly zero + */ + private int removeAllOccurrences(@Nullable Object element) { + try { + return unbox(countMap.remove(element)); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + /** + * Removes exactly the specified number of occurrences of {@code element}, or + * makes no change if this is not possible. + * + *

This method, in contrast to {@link #remove(Object, int)}, has no effect + * when the element count is smaller than {@code occurrences}. + * + * @param element the element to remove + * @param occurrences the number of occurrences of {@code element} to remove + * @return {@code true} if the removal was possible (including if {@code + * occurrences} is zero) + */ + public boolean removeExactly(@Nullable Object element, int occurrences) { + if (occurrences == 0) { + return true; + } + checkArgument(occurrences > 0, "Invalid occurrences: %s", occurrences); + + while (true) { + int current = count(element); + if (occurrences > current) { + return false; + } + if (occurrences == current) { + if (countMap.remove(element, occurrences)) { + return true; + } + } else { + @SuppressWarnings("unchecked") // it's in the map, must be an "E" + E casted = (E) element; + if (countMap.replace(casted, current, current - occurrences)) { + return true; + } + } + // If we're still here, there was a race, so just try again. + } + } + + /** + * Adds or removes occurrences of {@code element} such that the {@link #count} + * of the element becomes {@code count}. + * + * @return the count of {@code element} in the multiset before this call + * @throws IllegalArgumentException if {@code count} is negative + */ + @Override public int setCount(E element, int count) { + checkNonnegative(count, "count"); + return (count == 0) + ? removeAllOccurrences(element) + : unbox(countMap.put(element, count)); + } + + /** + * Sets the number of occurrences of {@code element} to {@code newCount}, but + * only if the count is currently {@code oldCount}. If {@code element} does + * not appear in the multiset exactly {@code oldCount} times, no changes will + * be made. + * + * @return {@code true} if the change was successful. This usually indicates + * that the multiset has been modified, but not always: in the case that + * {@code oldCount == newCount}, the method will return {@code true} if + * the condition was met. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is + * negative + */ + @Override public boolean setCount(E element, int oldCount, int newCount) { + checkNonnegative(oldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + if (newCount == 0) { + if (oldCount == 0) { + // No change to make, but must return true if the element is not present + return !countMap.containsKey(element); + } else { + return countMap.remove(element, oldCount); + } + } + if (oldCount == 0) { + return countMap.putIfAbsent(element, newCount) == null; + } + return countMap.replace(element, oldCount, newCount); + } + + // Views + + @Override Set createElementSet() { + final Set delegate = countMap.keySet(); + return new ForwardingSet() { + @Override protected Set delegate() { + return delegate; + } + @Override public boolean remove(Object object) { + try { + return delegate.remove(object); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + }; + } + + private transient EntrySet entrySet; + + @Override public Set> entrySet() { + EntrySet result = entrySet; + if (result == null) { + entrySet = result = new EntrySet(); + } + return result; + } + + private class EntrySet extends AbstractSet> { + @Override public int size() { + return countMap.size(); + } + + @Override public boolean isEmpty() { + return countMap.isEmpty(); + } + + @Override public boolean contains(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry entry = (Multiset.Entry) object; + Object element = entry.getElement(); + int entryCount = entry.getCount(); + return entryCount > 0 && count(element) == entryCount; + } + return false; + } + + @Override public Iterator> iterator() { + final Iterator> backingIterator + = countMap.entrySet().iterator(); + return new Iterator>() { + public boolean hasNext() { + return backingIterator.hasNext(); + } + + public Multiset.Entry next() { + Map.Entry backingEntry = backingIterator.next(); + return Multisets.immutableEntry( + backingEntry.getKey(), backingEntry.getValue()); + } + + public void remove() { + backingIterator.remove(); + } + }; + } + + /* + * Note: the superclass toArray() methods assume that size() gives a correct + * answer, which ours does not. + */ + + @Override public Object[] toArray() { + return snapshot().toArray(); + } + + @Override public T[] toArray(T[] array) { + return snapshot().toArray(array); + } + + /* + * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but + * either of these would recurse back to us again! + */ + private List> snapshot() { + List> list = Lists.newArrayListWithExpectedSize(size()); + for (Multiset.Entry entry : this) { + list.add(entry); + } + return list; + } + + @Override public boolean remove(Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry entry = (Multiset.Entry) object; + Object element = entry.getElement(); + int entryCount = entry.getCount(); + return countMap.remove(element, entryCount); + } + return false; + } + + @Override public void clear() { + countMap.clear(); + } + + /** + * The hash code is the same as countMap's, though the objects aren't equal. + */ + @Override public int hashCode() { + return countMap.hashCode(); + } + } + + /** + * We use a special form of unboxing that treats null as zero. + */ + private static int unbox(Integer i) { + return (i == null) ? 0 : i; + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + // creating HashMultiset to handle concurrent changes + Serialization.writeMultiset(HashMultiset.create(this), stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + FieldSettersHolder.COUNT_MAP_FIELD_SETTER.set( + this, new ConcurrentHashMap()); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0L; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ConstrainedMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ConstrainedMap.java new file mode 100644 index 00000000000..7aac2993409 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ConstrainedMap.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Factory and utilities pertaining to the {@code MapConstraint} interface. + * + * @author Mike Bostock + */ +@GwtCompatible +class ConstrainedMap extends ForwardingMap { + final Map delegate; + final MapConstraint constraint; + private volatile Set> entrySet; + + ConstrainedMap( + Map delegate, MapConstraint constraint) { + this.delegate = checkNotNull(delegate); + this.constraint = checkNotNull(constraint); + } + + @Override protected Map delegate() { + return delegate; + } + @Override public Set> entrySet() { + if (entrySet == null) { + entrySet = constrainedEntrySet(delegate.entrySet(), constraint); + } + return entrySet; + } + @Override public V put(K key, V value) { + constraint.checkKeyValue(key, value); + return delegate.put(key, value); + } + @Override public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + private static Entry constrainedEntry( + final Entry entry, + final MapConstraint constraint) { + checkNotNull(entry); + checkNotNull(constraint); + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return entry; + } + @Override public V setValue(V value) { + constraint.checkKeyValue(getKey(), value); + return entry.setValue(value); + } + }; + } + + private static Set> constrainedEntrySet( + Set> entries, + MapConstraint constraint) { + return new ConstrainedEntrySet(entries, constraint); + } + + private static class ConstrainedEntries + extends ForwardingCollection> { + final MapConstraint constraint; + final Collection> entries; + + ConstrainedEntries(Collection> entries, + MapConstraint constraint) { + this.entries = entries; + this.constraint = constraint; + } + @Override protected Collection> delegate() { + return entries; + } + + @Override public Iterator> iterator() { + final Iterator> iterator = entries.iterator(); + return new ForwardingIterator>() { + @Override public Entry next() { + return constrainedEntry(iterator.next(), constraint); + } + @Override protected Iterator> delegate() { + return iterator; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + @Override public boolean containsAll(Collection c) { + return Collections2.containsAll(this, c); + } + @Override public boolean remove(Object o) { + return Maps.removeEntryImpl(delegate(), o); + } + @Override public boolean removeAll(Collection c) { + return Iterators.removeAll(iterator(), c); + } + @Override public boolean retainAll(Collection c) { + return Iterators.retainAll(iterator(), c); + } + } + + static class ConstrainedEntrySet + extends ConstrainedEntries implements Set> { + ConstrainedEntrySet(Set> entries, + MapConstraint constraint) { + super(entries, constraint); + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public boolean equals(@Nullable Object object) { + return Collections2.setEquals(this, object); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/CustomConcurrentHashMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/CustomConcurrentHashMap.java new file mode 100644 index 00000000000..5b39a01f199 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/CustomConcurrentHashMap.java @@ -0,0 +1,2161 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.base.Function; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +import javax.annotation.Nullable; + +/** + * A framework for concurrent hash map implementations. The + * CustomConcurrentHashMap class itself is not extensible and does not contain + * any methods. Use {@link Builder} to create a custom concurrent hash map + * instance. Client libraries implement {@link Strategy}, and this class + * provides the surrounding concurrent data structure which implements {@link + * ConcurrentMap}. Additionally supports implementing maps where {@link + * Map#get} atomically computes values on demand (see {@link + * Builder#buildComputingMap(CustomConcurrentHashMap.ComputingStrategy, + * Function)}). + * + *

The resulting hash table supports full concurrency of retrievals and + * adjustable expected concurrency for updates. Even though all operations are + * thread-safe, retrieval operations do not entail locking, + * and there is not any support for locking the entire table + * in a way that prevents all access. + * + *

Retrieval operations (including {@link Map#get}) generally do not + * block, so may overlap with update operations (including + * {@link Map#put} and {@link Map#remove}). Retrievals reflect the results + * of the most recently completed update operations holding + * upon their onset. For aggregate operations such as {@link Map#putAll} + * and {@link Map#clear}, concurrent retrievals may reflect insertion or + * removal of only some entries. Similarly, iterators return elements + * reflecting the state of the hash table at some point at or since the + * creation of the iterator. They do not throw + * {@link java.util.ConcurrentModificationException}. However, iterators can + * only be used by one thread at a time. + * + *

The resulting {@link ConcurrentMap} and its views and iterators implement + * all of the optional methods of the {@link java.util.Map} and {@link + * java.util.Iterator} interfaces. Partially reclaimed entries are never + * exposed through the views or iterators. + * + *

For example, the following strategy emulates the behavior of + * {@link java.util.concurrent.ConcurrentHashMap}: + * + *

 {@code
+ * class ConcurrentHashMapStrategy
+ *     implements CustomConcurrentHashMap.Strategy>, Serializable {
+ *   public InternalEntry newEntry(K key, int hash,
+ *       InternalEntry next) {
+ *     return new InternalEntry(key, hash, null, next);
+ *   }
+ *   public InternalEntry copyEntry(K key,
+ *       InternalEntry original, InternalEntry next) {
+ *     return new InternalEntry(key, original.hash, original.value, next);
+ *   }
+ *   public void setValue(InternalEntry entry, V value) {
+ *     entry.value = value;
+ *   }
+ *   public V getValue(InternalEntry entry) { return entry.value; }
+ *   public boolean equalKeys(K a, Object b) { return a.equals(b); }
+ *   public boolean equalValues(V a, Object b) { return a.equals(b); }
+ *   public int hashKey(Object key) { return key.hashCode(); }
+ *   public K getKey(InternalEntry entry) { return entry.key; }
+ *   public InternalEntry getNext(InternalEntry entry) {
+ *     return entry.next;
+ *   }
+ *   public int getHash(InternalEntry entry) { return entry.hash; }
+ *   public void setInternals(CustomConcurrentHashMap.Internals> internals) {} // ignored
+ * }
+ *
+ * class InternalEntry {
+ *   final K key;
+ *   final int hash;
+ *   volatile V value;
+ *   final InternalEntry next;
+ *   InternalEntry(K key, int hash, V value, InternalEntry next) {
+ *     this.key = key;
+ *     this.hash = hash;
+ *     this.value = value;
+ *     this.next = next;
+ *   }
+ * }
+ * }
+ * + * To create a {@link java.util.concurrent.ConcurrentMap} using the strategy + * above: + * + *
{@code
+ *   ConcurrentMap map = new CustomConcurrentHashMap.Builder()
+ *       .build(new ConcurrentHashMapStrategy());
+ * }
+ * + * @author Bob Lee + * @author Doug Lea + */ +final class CustomConcurrentHashMap { + + /** Prevents instantiation. */ + private CustomConcurrentHashMap() {} + + /** + * Builds a custom concurrent hash map. + */ + static final class Builder { + private static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + private static final int UNSET_INITIAL_CAPACITY = -1; + private static final int UNSET_CONCURRENCY_LEVEL = -1; + + int initialCapacity = UNSET_INITIAL_CAPACITY; + int concurrencyLevel = UNSET_CONCURRENCY_LEVEL; + + /** + * Sets a custom initial capacity (defaults to 16). Resizing this or any + * other kind of hash table is a relatively slow operation, so, when + * possible, it is a good idea to provide estimates of expected table + * sizes. + * + * @throws IllegalArgumentException if initialCapacity < 0 + */ + public Builder initialCapacity(int initialCapacity) { + if (this.initialCapacity != UNSET_INITIAL_CAPACITY) { + throw new IllegalStateException( + "initial capacity was already set to " + this.initialCapacity); + } + if (initialCapacity < 0) { + throw new IllegalArgumentException(); + } + this.initialCapacity = initialCapacity; + return this; + } + + /** + * Guides the allowed concurrency among update operations. Used as a + * hint for internal sizing. The table is internally partitioned to try to + * permit the indicated number of concurrent updates without contention. + * Because placement in hash tables is essentially random, the actual + * concurrency will vary. Ideally, you should choose a value to accommodate + * as many threads as will ever concurrently modify the table. Using a + * significantly higher value than you need can waste space and time, + * and a significantly lower value can lead to thread contention. But + * overestimates and underestimates within an order of magnitude do + * not usually have much noticeable impact. A value of one is + * appropriate when it is known that only one thread will modify and + * all others will only read. Defaults to {@literal 16}. + * + * @throws IllegalArgumentException if concurrencyLevel < 0 + */ + public Builder concurrencyLevel(int concurrencyLevel) { + if (this.concurrencyLevel != UNSET_CONCURRENCY_LEVEL) { + throw new IllegalStateException( + "concurrency level was already set to " + this.concurrencyLevel); + } + if (concurrencyLevel <= 0) { + throw new IllegalArgumentException(); + } + this.concurrencyLevel = concurrencyLevel; + return this; + } + + /** + * Creates a new concurrent hash map backed by the given strategy. + * + * @param strategy used to implement and manipulate the entries + * + * @param the type of keys to be stored in the returned map + * @param the type of values to be stored in the returned map + * @param the type of internal entry to be stored in the returned map + * + * @throws NullPointerException if strategy is null + */ + public ConcurrentMap buildMap(Strategy strategy) { + if (strategy == null) { + throw new NullPointerException("strategy"); + } + return new Impl(strategy, this); + } + + /** + * Creates a {@link ConcurrentMap}, backed by the given strategy, that + * supports atomic, on-demand computation of values. {@link Map#get} + * returns the value corresponding to the given key, atomically computes + * it using the computer function passed to this builder, or waits for + * another thread to compute the value if necessary. Only one value will + * be computed for each key at a given time. + * + *

If an entry's value has not finished computing yet, query methods + * besides {@link java.util.Map#get} return immediately as if an entry + * doesn't exist. In other words, an entry isn't externally visible until + * the value's computation completes. + * + *

{@link Map#get} in the returned map implementation throws: + *

    + *
  • {@link NullPointerException} if the key is null or the + * computer returns null
  • + *
  • or {@link ComputationException} wrapping an exception thrown by the + * computation
  • + *
+ * + *

Note: Callers of {@code get()} must ensure that the key + * argument is of type {@code K}. {@code Map.get()} takes {@code Object}, + * so the key type is not checked at compile time. Passing an object of + * a type other than {@code K} can result in that object being unsafely + * passed to the computer function as type {@code K} not to mention the + * unsafe key being stored in the map. + * + * @param strategy used to implement and manipulate the entries + * @param computer used to compute values for keys + * + * @param the type of keys to be stored in the returned map + * @param the type of values to be stored in the returned map + * @param the type of internal entry to be stored in the returned map + * + * @throws NullPointerException if strategy or computer is null + */ + public ConcurrentMap buildComputingMap( + ComputingStrategy strategy, + Function computer) { + if (strategy == null) { + throw new NullPointerException("strategy"); + } + if (computer == null) { + throw new NullPointerException("computer"); + } + + return new ComputingImpl(strategy, this, computer); + } + + int getInitialCapacity() { + return (initialCapacity == UNSET_INITIAL_CAPACITY) + ? DEFAULT_INITIAL_CAPACITY : initialCapacity; + } + + int getConcurrencyLevel() { + return (concurrencyLevel == UNSET_CONCURRENCY_LEVEL) + ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel; + } + } + + /** + * Implements behavior specific to the client's concurrent hash map + * implementation. Used by the framework to create new entries and perform + * operations on them. + * + *

Method parameters are never null unless otherwise specified. + * + *

Partially Reclaimed Entries

+ * + *

This class does not allow {@code null} to be used as a key. + * Setting values to null is not permitted, but entries may have null keys + * or values for various reasons. For example, the key or value may have + * been garbage collected or reclaimed through other means. + * CustomConcurrentHashMap treats entries with null keys and values as + * "partially reclaimed" and ignores them for the most part. Entries may + * enter a partially reclaimed state but they must not leave it. Partially + * reclaimed entries will not be copied over during table expansions, for + * example. Strategy implementations should proactively remove partially + * reclaimed entries so that {@link Map#isEmpty} and {@link Map#size()} + * return up-to-date results. + * + * @param the type of keys to be stored in the returned map + * @param the type of values to be stored in the returned map + * @param internal entry type, not directly exposed to clients in map + * views + */ + public interface Strategy { + + /** + * Constructs a new entry for the given key with a pointer to the given + * next entry. + * + *

This method may return different entry implementations + * depending upon whether next is null or not. For example, if next is + * null (as will often be the case), this factory might use an entry + * class that doesn't waste memory on an unnecessary field. + * + * @param key for this entry + * @param hash of key returned by {@link #hashKey} + * @param next entry (used when implementing a hash bucket as a linked + * list, for example), possibly null + * @return a new entry + */ + abstract E newEntry(K key, int hash, E next); + + /** + * Creates a copy of the given entry pointing to the given next entry. + * Copies the value and any other implementation-specific state from + * {@code original} to the returned entry. For example, + * CustomConcurrentHashMap might use this method when removing other + * entries or expanding the internal table. + * + * @param key for {@code original} as well as the returned entry. + * Explicitly provided here to prevent reclamation of the key at an + * inopportune time in implementations that don't otherwise keep + * a strong reference to the key. + * @param original entry from which the value and other + * implementation-specific state should be copied + * @param newNext the next entry the new entry should point to, possibly + * null + */ + E copyEntry(K key, E original, E newNext); + + /** + * Sets the value of an entry using volatile semantics. Values are set + * synchronously on a per-entry basis. + * + * @param entry to set the value on + * @param value to set + */ + void setValue(E entry, V value); + + /** + * Gets the value of an entry using volatile semantics. + * + * @param entry to get the value from + */ + V getValue(E entry); + + /** + * Returns true if the two given keys are equal, false otherwise. Neither + * key will be null. + * + * @param a key from inside the map + * @param b key passed from caller, not necesarily of type K + * + * @see Object#equals the same contractual obligations apply here + */ + boolean equalKeys(K a, Object b); + + /** + * Returns true if the two given values are equal, false otherwise. Neither + * value will be null. + * + * @param a value from inside the map + * @param b value passed from caller, not necesarily of type V + * + * @see Object#equals the same contractual obligations apply here + */ + boolean equalValues(V a, Object b); + + /** + * Returns a hash code for the given key. + * + * @see Object#hashCode the same contractual obligations apply here + */ + int hashKey(Object key); + + /** + * Gets the key for the given entry. This may return null (for example, + * if the key was reclaimed by the garbage collector). + * + * @param entry to get key from + * @return key from the given entry + */ + K getKey(E entry); + + /** + * Gets the next entry relative to the given entry, the exact same entry + * that was provided to {@link Strategy#newEntry} when the given entry was + * created. + * + * @return the next entry or null if null was passed to + * {@link Strategy#newEntry} + */ + E getNext(E entry); + + /** + * Returns the hash code that was passed to {@link Strategy#newEntry}) + * when the given entry was created. + */ + int getHash(E entry); + +// TODO: +// /** +// * Notifies the strategy that an entry has been removed from the map. +// * +// * @param entry that was recently removed +// */ +// void remove(E entry); + + /** + * Provides an API for interacting directly with the map's internal + * entries to this strategy. Invoked once when the map is created. + * Strategies that don't need access to the map's internal entries + * can simply ignore the argument. + * + * @param internals of the map, enables direct interaction with the + * internal entries + */ + void setInternals(Internals internals); + } + + /** + * Provides access to a map's internal entries. + */ + public interface Internals { + +// TODO: +// /** +// * Returns a set view of the internal entries. +// */ +// Set entrySet(); + + /** + * Returns the internal entry corresponding to the given key from the map. + * + * @param key to retrieve entry for + * + * @throws NullPointerException if key is null + */ + E getEntry(K key); + + /** + * Removes the given entry from the map if the value of the entry in the + * map matches the given value. + * + * @param entry to remove + * @param value entry must have for the removal to succeed + * + * @throws NullPointerException if entry is null + */ + boolean removeEntry(E entry, @Nullable V value); + + /** + * Removes the given entry from the map. + * + * @param entry to remove + * + * @throws NullPointerException if entry is null + */ + boolean removeEntry(E entry); + } + + /** + * Extends {@link Strategy} to add support for computing values on-demand. + * Implementations should typically intialize the entry's value to a + * placeholder value in {@link #newEntry(Object, int, Object)} so that + * {@link #waitForValue(Object)} can tell the difference between a + * pre-intialized value and an in-progress computation. {@link + * #copyEntry(Object, Object, Object)} must detect and handle the case where + * an entry is copied in the middle of a computation. Implementations can + * throw {@link UnsupportedOperationException} in {@link #setValue(Object, + * Object)} if they wish to prevent users from setting values directly. + * + * @see Builder#buildComputingMap(CustomConcurrentHashMap.ComputingStrategy, + * Function) + */ + public interface ComputingStrategy extends Strategy { + + /** + * Computes a value for the given key and stores it in the given entry. + * Called as a result of {@link Map#get}. If this method throws an + * exception, CustomConcurrentHashMap will remove the entry and retry + * the computation on subsequent requests. + * + * @param entry that was created + * @param computer passed to {@link Builder#buildMap} + * + * @throws ComputationException if the computation threw an exception + * @throws NullPointerException if the computer returned null + * + * @return the computed value + */ + V compute(K key, E entry, Function computer); + + /** + * Gets a value from an entry waiting for the value to be set by {@link + * #compute} if necessary. Returns null if a value isn't available at + * which point CustomConcurrentHashMap tries to compute a new value. + * + * @param entry to return value from + * @return stored value or null if the value isn't available + * + * @throws InterruptedException if the thread was interrupted while + * waiting + */ + V waitForValue(E entry) throws InterruptedException; + } + + /** + * Applies a supplemental hash function to a given hash code, which defends + * against poor quality hash functions. This is critical when the + * concurrent hash map uses power-of-two length hash tables, that otherwise + * encounter collisions for hash codes that do not differ in lower or upper + * bits. + * + * @param h hash code + */ + private static int rehash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + h += (h << 15) ^ 0xffffcd7d; + h ^= (h >>> 10); + h += (h << 3); + h ^= (h >>> 6); + h += (h << 2) + (h << 14); + return h ^ (h >>> 16); + } + + /** The concurrent hash map implementation. */ + static class Impl extends AbstractMap + implements ConcurrentMap, Serializable { + + /* + * The basic strategy is to subdivide the table among Segments, + * each of which itself is a concurrently readable hash table. + */ + + /* ---------------- Constants -------------- */ + + /** + * 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 to ensure that entries are indexable using ints. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The maximum number of segments to allow; used to bound constructor + * arguments. + */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** + * Number of unsynchronized retries in size and containsValue methods before + * resorting to locking. This is used to avoid unbounded retries if tables + * undergo continuous modification which would make it impossible to obtain + * an accurate result. + */ + static final int RETRIES_BEFORE_LOCK = 2; + + /* ---------------- Fields -------------- */ + + /** + * The strategy used to implement this map. + */ + final Strategy strategy; + + /** + * Mask value for indexing into segments. The upper bits of a key's hash + * code are used to choose the segment. + */ + final int segmentMask; + + /** + * Shift value for indexing within segments. Helps prevent entries that + * end up in the same segment from also ending up in the same bucket. + */ + final int segmentShift; + + /** + * The segments, each of which is a specialized hash table + */ + final Segment[] segments; + + /** + * Creates a new, empty map with the specified strategy, initial capacity, + * load factor and concurrency level. + */ + Impl(Strategy strategy, Builder builder) { + int concurrencyLevel = builder.getConcurrencyLevel(); + int initialCapacity = builder.getInitialCapacity(); + + if (concurrencyLevel > MAX_SEGMENTS) { + concurrencyLevel = MAX_SEGMENTS; + } + + // Find power-of-two sizes best matching arguments + int segmentShift = 0; + int segmentCount = 1; + while (segmentCount < concurrencyLevel) { + ++segmentShift; + segmentCount <<= 1; + } + this.segmentShift = 32 - segmentShift; + segmentMask = segmentCount - 1; + this.segments = newSegmentArray(segmentCount); + + if (initialCapacity > MAXIMUM_CAPACITY) { + initialCapacity = MAXIMUM_CAPACITY; + } + + int segmentCapacity = initialCapacity / segmentCount; + if (segmentCapacity * segmentCount < initialCapacity) { + ++segmentCapacity; + } + + int segmentSize = 1; + while (segmentSize < segmentCapacity) { + segmentSize <<= 1; + } + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = new Segment(segmentSize); + } + + this.strategy = strategy; + + strategy.setInternals(new InternalsImpl()); + } + + int hash(Object key) { + int h = strategy.hashKey(key); + return rehash(h); + } + + class InternalsImpl implements Internals, Serializable { + + static final long serialVersionUID = 0; + + public E getEntry(K key) { + if (key == null) { + throw new NullPointerException("key"); + } + int hash = hash(key); + return segmentFor(hash).getEntry(key, hash); + } + + public boolean removeEntry(E entry, V value) { + if (entry == null) { + throw new NullPointerException("entry"); + } + int hash = strategy.getHash(entry); + return segmentFor(hash).removeEntry(entry, hash, value); + } + + public boolean removeEntry(E entry) { + if (entry == null) { + throw new NullPointerException("entry"); + } + int hash = strategy.getHash(entry); + return segmentFor(hash).removeEntry(entry, hash); + } + } + + @SuppressWarnings("unchecked") + Segment[] newSegmentArray(int ssize) { + // Note: This is the only way I could figure out how to create + // a segment array (the compile has a tough time with arrays of + // inner classes of generic types apparently). Safe because we're + // restricting what can go in the array and no one has an + // unrestricted reference. + return (Segment[]) Array.newInstance(Segment.class, ssize); + } + + /* ---------------- Small Utilities -------------- */ + + /** + * Returns the segment that should be used for key with given hash + * + * @param hash the hash code for the key + * @return the segment + */ + Segment segmentFor(int hash) { + return segments[(hash >>> segmentShift) & segmentMask]; + } + + /* ---------------- Inner Classes -------------- */ + + /** + * Segments are specialized versions of hash tables. This subclasses from + * ReentrantLock opportunistically, just to simplify some locking and avoid + * separate construction. + */ + @SuppressWarnings("serial") // This class is never serialized. + final class Segment extends ReentrantLock { + + /* + * Segments maintain a table of entry lists that are ALWAYS + * kept in a consistent state, so can be read without locking. + * Next fields of nodes are immutable (final). All list + * additions are performed at the front of each bin. This + * makes it easy to check changes, and also fast to traverse. + * When nodes would otherwise be changed, new nodes are + * created to replace them. This works well for hash tables + * since the bin lists tend to be short. (The average length + * is less than two for the default load factor threshold.) + * + * Read operations can thus proceed without locking, but rely + * on selected uses of volatiles to ensure that completed + * write operations performed by other threads are + * noticed. For most purposes, the "count" field, tracking the + * number of elements, serves as that volatile variable + * ensuring visibility. This is convenient because this field + * needs to be read in many read operations anyway: + * + * - All (unsynchronized) read operations must first read the + * "count" field, and should not look at table entries if + * it is 0. + * + * - All (synchronized) write operations should write to + * the "count" field after structurally changing any bin. + * The operations must not take any action that could even + * momentarily cause a concurrent read operation to see + * inconsistent data. This is made easier by the nature of + * the read operations in Map. For example, no operation + * can reveal that the table has grown but the threshold + * has not yet been updated, so there are no atomicity + * requirements for this with respect to reads. + * + * As a guide, all critical volatile reads and writes to the + * count field are marked in code comments. + */ + + /** + * The number of elements in this segment's region. + */ + volatile int count; + + /** + * Number of updates that alter the size of the table. This is used + * during bulk-read methods to make sure they see a consistent snapshot: + * If modCounts change during a traversal of segments computing size or + * checking containsValue, then we might have an inconsistent view of + * state so (usually) must retry. + */ + int modCount; + + /** + * The table is expanded when its size exceeds this threshold. (The + * value of this field is always {@code (int)(capacity * loadFactor)}.) + */ + int threshold; + + /** + * The per-segment table. + */ + volatile AtomicReferenceArray table; + + Segment(int initialCapacity) { + setTable(newEntryArray(initialCapacity)); + } + + AtomicReferenceArray newEntryArray(int size) { + return new AtomicReferenceArray(size); + } + + /** + * Sets table to new HashEntry array. Call only while holding lock or in + * constructor. + */ + void setTable(AtomicReferenceArray newTable) { + this.threshold = newTable.length() * 3 / 4; + this.table = newTable; + } + + /** + * Returns properly casted first entry of bin for given hash. + */ + E getFirst(int hash) { + AtomicReferenceArray table = this.table; + return table.get(hash & (table.length() - 1)); + } + + /* Specialized implementations of map methods */ + + public E getEntry(Object key, int hash) { + Strategy s = Impl.this.strategy; + if (count != 0) { // read-volatile + for (E e = getFirst(hash); e != null; e = s.getNext(e)) { + if (s.getHash(e) != hash) { + continue; + } + + K entryKey = s.getKey(e); + if (entryKey == null) { + continue; + } + + if (s.equalKeys(entryKey, key)) { + return e; + } + } + } + + return null; + } + + V get(Object key, int hash) { + E entry = getEntry(key, hash); + if (entry == null) { + return null; + } + + return strategy.getValue(entry); + } + + boolean containsKey(Object key, int hash) { + Strategy s = Impl.this.strategy; + if (count != 0) { // read-volatile + for (E e = getFirst(hash); e != null; e = s.getNext(e)) { + if (s.getHash(e) != hash) { + continue; + } + + K entryKey = s.getKey(e); + if (entryKey == null) { + continue; + } + + if (s.equalKeys(entryKey, key)) { + // Return true only if this entry has a value. + return s.getValue(e) != null; + } + } + } + + return false; + } + + boolean containsValue(Object value) { + Strategy s = Impl.this.strategy; + if (count != 0) { // read-volatile + AtomicReferenceArray table = this.table; + int length = table.length(); + for (int i = 0; i < length; i++) { + for (E e = table.get(i); e != null; e = s.getNext(e)) { + V entryValue = s.getValue(e); + + // If the value disappeared, this entry is partially collected, + // and we should skip it. + if (entryValue == null) { + continue; + } + + if (s.equalValues(entryValue, value)) { + return true; + } + } + } + } + + return false; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + Strategy s = Impl.this.strategy; + lock(); + try { + for (E e = getFirst(hash); e != null; e = s.getNext(e)) { + K entryKey = s.getKey(e); + if (s.getHash(e) == hash && entryKey != null + && s.equalKeys(key, entryKey)) { + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + V entryValue = s.getValue(e); + if (entryValue == null) { + return false; + } + + if (s.equalValues(entryValue, oldValue)) { + s.setValue(e, newValue); + return true; + } + } + } + + return false; + } finally { + unlock(); + } + } + + V replace(K key, int hash, V newValue) { + Strategy s = Impl.this.strategy; + lock(); + try { + for (E e = getFirst(hash); e != null; e = s.getNext(e)) { + K entryKey = s.getKey(e); + if (s.getHash(e) == hash && entryKey != null + && s.equalKeys(key, entryKey)) { + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + V entryValue = s.getValue(e); + if (entryValue == null) { + return null; + } + + s.setValue(e, newValue); + return entryValue; + } + } + + return null; + } finally { + unlock(); + } + } + + V put(K key, int hash, V value, boolean onlyIfAbsent) { + Strategy s = Impl.this.strategy; + lock(); + try { + int count = this.count; + if (count++ > this.threshold) { // ensure capacity + expand(); + } + + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + + E first = table.get(index); + + // Look for an existing entry. + for (E e = first; e != null; e = s.getNext(e)) { + K entryKey = s.getKey(e); + if (s.getHash(e) == hash && entryKey != null + && s.equalKeys(key, entryKey)) { + // We found an existing entry. + + // If the value disappeared, this entry is partially collected, + // and we should pretend like it doesn't exist. + V entryValue = s.getValue(e); + if (onlyIfAbsent && entryValue != null) { + return entryValue; + } + + s.setValue(e, value); + return entryValue; + } + } + + // Create a new entry. + ++modCount; + E newEntry = s.newEntry(key, hash, first); + s.setValue(newEntry, value); + table.set(index, newEntry); + this.count = count; // write-volatile + return null; + } finally { + unlock(); + } + } + + /** + * Expands the table if possible. + */ + void expand() { + AtomicReferenceArray oldTable = table; + int oldCapacity = oldTable.length(); + if (oldCapacity >= MAXIMUM_CAPACITY) { + return; + } + + /* + * Reclassify nodes in each list to new Map. Because we are + * using power-of-two expansion, the elements from each bin + * must either stay at same index, or move with a power of two + * offset. We eliminate unnecessary node creation by catching + * cases where old nodes can be reused because their next + * fields won't change. Statistically, at the default + * threshold, only about one-sixth of them need cloning when + * a table doubles. The nodes they replace will be garbage + * collectable as soon as they are no longer referenced by any + * reader thread that may be in the midst of traversing table + * right now. + */ + + Strategy s = Impl.this.strategy; + AtomicReferenceArray newTable = newEntryArray(oldCapacity << 1); + threshold = newTable.length() * 3 / 4; + int newMask = newTable.length() - 1; + for (int oldIndex = 0; oldIndex < oldCapacity; oldIndex++) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + E head = oldTable.get(oldIndex); + + if (head != null) { + E next = s.getNext(head); + int headIndex = s.getHash(head) & newMask; + + // Single node on list + if (next == null) { + newTable.set(headIndex, head); + } else { + // Reuse the consecutive sequence of nodes with the same target + // index from the end of the list. tail points to the first + // entry in the reusable list. + E tail = head; + int tailIndex = headIndex; + for (E last = next; last != null; last = s.getNext(last)) { + int newIndex = s.getHash(last) & newMask; + if (newIndex != tailIndex) { + // The index changed. We'll need to copy the previous entry. + tailIndex = newIndex; + tail = last; + } + } + newTable.set(tailIndex, tail); + + // Clone nodes leading up to the tail. + for (E e = head; e != tail; e = s.getNext(e)) { + K key = s.getKey(e); + if (key != null) { + int newIndex = s.getHash(e) & newMask; + E newNext = newTable.get(newIndex); + newTable.set(newIndex, s.copyEntry(key, e, newNext)); + } else { + // Key was reclaimed. Skip entry. + } + } + } + } + } + table = newTable; + } + + V remove(Object key, int hash) { + Strategy s = Impl.this.strategy; + lock(); + try { + int count = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = s.getNext(e)) { + K entryKey = s.getKey(e); + if (s.getHash(e) == hash && entryKey != null + && s.equalKeys(entryKey, key)) { + V entryValue = strategy.getValue(e); + // All entries following removed node can stay + // in list, but all preceding ones need to be + // cloned. + ++modCount; + E newFirst = s.getNext(e); + for (E p = first; p != e; p = s.getNext(p)) { + K pKey = s.getKey(p); + if (pKey != null) { + newFirst = s.copyEntry(pKey, p, newFirst); + } else { + // Key was reclaimed. Skip entry. + } + } + table.set(index, newFirst); + this.count = count; // write-volatile + return entryValue; + } + } + + return null; + } finally { + unlock(); + } + } + + boolean remove(Object key, int hash, Object value) { + Strategy s = Impl.this.strategy; + lock(); + try { + int count = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = s.getNext(e)) { + K entryKey = s.getKey(e); + if (s.getHash(e) == hash && entryKey != null + && s.equalKeys(entryKey, key)) { + V entryValue = strategy.getValue(e); + if (value == entryValue || (value != null && entryValue != null + && s.equalValues(entryValue, value))) { + // All entries following removed node can stay + // in list, but all preceding ones need to be + // cloned. + ++modCount; + E newFirst = s.getNext(e); + for (E p = first; p != e; p = s.getNext(p)) { + K pKey = s.getKey(p); + if (pKey != null) { + newFirst = s.copyEntry(pKey, p, newFirst); + } else { + // Key was reclaimed. Skip entry. + } + } + table.set(index, newFirst); + this.count = count; // write-volatile + return true; + } else { + return false; + } + } + } + + return false; + } finally { + unlock(); + } + } + + public boolean removeEntry(E entry, int hash, V value) { + Strategy s = Impl.this.strategy; + lock(); + try { + int count = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = s.getNext(e)) { + if (s.getHash(e) == hash && entry.equals(e)) { + V entryValue = s.getValue(e); + if (entryValue == value || (value != null + && s.equalValues(entryValue, value))) { + // All entries following removed node can stay + // in list, but all preceding ones need to be + // cloned. + ++modCount; + E newFirst = s.getNext(e); + for (E p = first; p != e; p = s.getNext(p)) { + K pKey = s.getKey(p); + if (pKey != null) { + newFirst = s.copyEntry(pKey, p, newFirst); + } else { + // Key was reclaimed. Skip entry. + } + } + table.set(index, newFirst); + this.count = count; // write-volatile + return true; + } else { + return false; + } + } + } + + return false; + } finally { + unlock(); + } + } + + public boolean removeEntry(E entry, int hash) { + Strategy s = Impl.this.strategy; + lock(); + try { + int count = this.count - 1; + AtomicReferenceArray table = this.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + + for (E e = first; e != null; e = s.getNext(e)) { + if (s.getHash(e) == hash && entry.equals(e)) { + // All entries following removed node can stay + // in list, but all preceding ones need to be + // cloned. + ++modCount; + E newFirst = s.getNext(e); + for (E p = first; p != e; p = s.getNext(p)) { + K pKey = s.getKey(p); + if (pKey != null) { + newFirst = s.copyEntry(pKey, p, newFirst); + } else { + // Key was reclaimed. Skip entry. + } + } + table.set(index, newFirst); + this.count = count; // write-volatile + return true; + } + } + + return false; + } finally { + unlock(); + } + } + + void clear() { + if (count != 0) { + lock(); + try { + AtomicReferenceArray table = this.table; + for (int i = 0; i < table.length(); i++) { + table.set(i, null); + } + ++modCount; + count = 0; // write-volatile + } finally { + unlock(); + } + } + } + } + + /* ---------------- Public operations -------------- */ + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings + */ + @Override public boolean isEmpty() { + final Segment[] segments = this.segments; + /* + * We keep track of per-segment modCounts to avoid ABA + * problems in which an element in one segment was added and + * in another removed during traversal, in which case the + * table was never actually empty at any point. Note the + * similar use of modCounts in the size() and containsValue() + * methods, which are the only other methods also susceptible + * to ABA problems. + */ + int[] mc = new int[segments.length]; + int mcsum = 0; + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } else { + mcsum += mc[i] = segments[i].modCount; + } + } + // If mcsum happens to be zero, then we know we got a snapshot + // before any modifications at all were made. This is + // probably common enough to bother tracking. + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0 || + mc[i] != segments[i].modCount) { + return false; + } + } + } + return true; + } + + /** + * Returns the number of key-value mappings in this map. If the map + * contains more than {@code Integer.MAX_VALUE} elements, returns + * {@code Integer.MAX_VALUE}. + * + * @return the number of key-value mappings in this map + */ + @Override public int size() { + final Segment[] segments = this.segments; + long sum = 0; + long check = 0; + int[] mc = new int[segments.length]; + // Try a few times to get accurate count. On failure due to + // continuous async changes in table, resort to locking. + for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { + check = 0; + sum = 0; + int mcsum = 0; + for (int i = 0; i < segments.length; ++i) { + sum += segments[i].count; + mcsum += mc[i] = segments[i].modCount; + } + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++i) { + check += segments[i].count; + if (mc[i] != segments[i].modCount) { + check = -1; // force retry + break; + } + } + } + if (check == sum) { + break; + } + } + if (check != sum) { // Resort to locking all segments + sum = 0; + for (Segment segment : segments) { + segment.lock(); + } + for (Segment segment : segments) { + sum += segment.count; + } + for (Segment segment : segments) { + segment.unlock(); + } + } + if (sum > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) sum; + } + } + + /** + * 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.equals(k)}, then this method + * returns {@code v}; otherwise it returns {@code null}. (There can be at + * most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @Override public V get(Object key) { + if (key == null) { + throw new NullPointerException("key"); + } + int hash = hash(key); + return segmentFor(hash).get(key, hash); + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return {@code true} if and only if the specified object is a key in + * this table, as determined by the {@code equals} method; + * {@code false} otherwise. + * @throws NullPointerException if the specified key is null + */ + @Override public boolean containsKey(Object key) { + if (key == null) { + throw new NullPointerException("key"); + } + int hash = hash(key); + return segmentFor(hash).containsKey(key, hash); + } + + /** + * Returns {@code true} if this map maps one or more keys to the specified + * value. Note: This method requires a full internal traversal of the hash + * table, and so is much slower than method {@code containsKey}. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the specified + * value + * @throws NullPointerException if the specified value is null + */ + @Override public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException("value"); + } + + // See explanation of modCount use above + + final Segment[] segments = this.segments; + int[] mc = new int[segments.length]; + + // Try a few times without locking + for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { + int mcsum = 0; + for (int i = 0; i < segments.length; ++i) { + @SuppressWarnings("UnusedDeclaration") + int c = segments[i].count; + mcsum += mc[i] = segments[i].modCount; + if (segments[i].containsValue(value)) { + return true; + } + } + boolean cleanSweep = true; + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++i) { + @SuppressWarnings("UnusedDeclaration") + int c = segments[i].count; + if (mc[i] != segments[i].modCount) { + cleanSweep = false; + break; + } + } + } + if (cleanSweep) { + return false; + } + } + // Resort to locking all segments + for (Segment segment : segments) { + segment.lock(); + } + boolean found = false; + try { + for (Segment segment : segments) { + if (segment.containsValue(value)) { + found = true; + break; + } + } + } finally { + for (Segment segment : segments) { + segment.unlock(); + } + } + return found; + } + + /** + * Maps the specified key to the specified value in this table. Neither the + * key nor the value can be null. + * + *

The value can be retrieved by calling the {@code get} method with a + * key that is equal to the original key. + * + * @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 {@code key}, or {@code null} + * if there was no mapping for {@code key} + * @throws NullPointerException if the specified key or value is null + */ + @Override public V put(K key, V value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, false); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, or + * {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + public V putIfAbsent(K key, V value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + int hash = hash(key); + return segmentFor(hash).put(key, hash, value, true); + } + + /** + * Copies all of the mappings from the specified map to this one. These + * mappings 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 + */ + @Override public void putAll(Map m) { + for (Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + /** + * Removes the key (and its corresponding value) from this map. This method + * does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with {@code key}, or {@code null} + * if there was no mapping for {@code key} + * @throws NullPointerException if the specified key is null + */ + @Override public V remove(Object key) { + if (key == null) { + throw new NullPointerException("key"); + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + if (key == null) { + throw new NullPointerException("key"); + } + int hash = hash(key); + return segmentFor(hash).remove(key, hash, value); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (key == null) { + throw new NullPointerException("key"); + } + if (oldValue == null) { + throw new NullPointerException("oldValue"); + } + if (newValue == null) { + throw new NullPointerException("newValue"); + } + int hash = hash(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, or + * {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + public V replace(K key, V value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + int hash = hash(key); + return segmentFor(hash).replace(key, hash, value); + } + + /** + * Removes all of the mappings from this map. + */ + @Override public void clear() { + for (Segment segment : segments) { + segment.clear(); + } + } + + Set keySet; + + /** + * Returns a {@link java.util.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. The set supports element removal, which removes the + * corresponding mapping from this map, via the {@code Iterator.remove}, + * {@code Set.remove}, {@code removeAll}, {@code retainAll}, and + * {@code clear} operations. It does not support the {@code add} or + * {@code addAll} operations. + * + *

The view's {@code iterator} is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications + * subsequent to construction. + */ + @Override public Set keySet() { + Set ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet()); + } + + Collection values; + + /** + * Returns a {@link java.util.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. The collection supports + * element removal, which removes the corresponding mapping from this map, + * via the {@code Iterator.remove}, {@code Collection.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} operations. It + * does not support the {@code add} or {@code addAll} operations. + * + *

The view's {@code iterator} is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications + * subsequent to construction. + */ + @Override public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + Set> entrySet; + + /** + * Returns a {@link java.util.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. The set supports element removal, which removes + * the corresponding mapping from the map, via the {@code Iterator.remove}, + * {@code Set.remove}, {@code removeAll}, {@code retainAll}, and + * {@code clear} operations. It does not support the {@code add} or + * {@code addAll} operations. + * + *

The view's {@code iterator} is a "weakly consistent" iterator that + * will never throw {@link java.util.ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications + * subsequent to construction. + */ + @Override public Set> entrySet() { + Set> es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet()); + } + + /* ---------------- Iterator Support -------------- */ + + abstract class HashIterator { + + int nextSegmentIndex; + int nextTableIndex; + AtomicReferenceArray currentTable; + E nextEntry; + WriteThroughEntry nextExternal; + WriteThroughEntry lastReturned; + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + public boolean hasMoreElements() { + return hasNext(); + } + + final void advance() { + nextExternal = null; + + if (nextInChain()) { + return; + } + + if (nextInTable()) { + return; + } + + while (nextSegmentIndex >= 0) { + Segment seg = segments[nextSegmentIndex--]; + if (seg.count != 0) { + currentTable = seg.table; + nextTableIndex = currentTable.length() - 1; + if (nextInTable()) { + return; + } + } + } + } + + /** + * Finds the next entry in the current chain. Returns true if an entry + * was found. + */ + boolean nextInChain() { + Strategy s = Impl.this.strategy; + if (nextEntry != null) { + for (nextEntry = s.getNext(nextEntry); nextEntry != null; + nextEntry = s.getNext(nextEntry)) { + if (advanceTo(nextEntry)) { + return true; + } + } + } + return false; + } + + /** + * Finds the next entry in the current table. Returns true if an entry + * was found. + */ + boolean nextInTable() { + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable.get(nextTableIndex--)) != null) { + if (advanceTo(nextEntry) || nextInChain()) { + return true; + } + } + } + return false; + } + + /** + * Advances to the given entry. Returns true if the entry was valid, + * false if it should be skipped. + */ + boolean advanceTo(E entry) { + Strategy s = Impl.this.strategy; + K key = s.getKey(entry); + V value = s.getValue(entry); + if (key != null && value != null) { + nextExternal = new WriteThroughEntry(key, value); + return true; + } else { + // Skip partially reclaimed entry. + return false; + } + } + + public boolean hasNext() { + return nextExternal != null; + } + + WriteThroughEntry nextEntry() { + if (nextExternal == null) { + throw new NoSuchElementException(); + } + lastReturned = nextExternal; + advance(); + return lastReturned; + } + + public void remove() { + if (lastReturned == null) { + throw new IllegalStateException(); + } + Impl.this.remove(lastReturned.getKey()); + lastReturned = null; + } + } + + final class KeyIterator extends HashIterator implements Iterator { + + public K next() { + return super.nextEntry().getKey(); + } + } + + final class ValueIterator extends HashIterator implements Iterator { + + public V next() { + return super.nextEntry().getValue(); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue + * changes to the underlying map. + */ + final class WriteThroughEntry extends AbstractMapEntry { + final K key; + V value; + + WriteThroughEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override public K getKey() { + return key; + } + + @Override public V getValue() { + return value; + } + + /** + * Set our entry's value and write through to the map. The value to + * return is somewhat arbitrary here. Since a WriteThroughEntry does not + * necessarily track asynchronous changes, the most recent "previous" + * value could be different from what we return (or could even have been + * removed in which case the put will re-establish). We do not and + * cannot guarantee more. + */ + @Override public V setValue(V value) { + if (value == null) { + throw new NullPointerException(); + } + V oldValue = Impl.this.put(getKey(), value); + this.value = value; + return oldValue; + } + } + + final class EntryIterator extends HashIterator + implements Iterator> { + + public Entry next() { + return nextEntry(); + } + } + + final class KeySet extends AbstractSet { + + @Override public Iterator iterator() { + return new KeyIterator(); + } + + @Override public int size() { + return Impl.this.size(); + } + + @Override public boolean isEmpty() { + return Impl.this.isEmpty(); + } + + @Override public boolean contains(Object o) { + return Impl.this.containsKey(o); + } + + @Override public boolean remove(Object o) { + return Impl.this.remove(o) != null; + } + + @Override public void clear() { + Impl.this.clear(); + } + } + + final class Values extends AbstractCollection { + + @Override public Iterator iterator() { + return new ValueIterator(); + } + + @Override public int size() { + return Impl.this.size(); + } + + @Override public boolean isEmpty() { + return Impl.this.isEmpty(); + } + + @Override public boolean contains(Object o) { + return Impl.this.containsValue(o); + } + + @Override public void clear() { + Impl.this.clear(); + } + } + + final class EntrySet extends AbstractSet> { + + @Override public Iterator> iterator() { + return new EntryIterator(); + } + + @Override public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + if (key == null) { + return false; + } + V v = Impl.this.get(key); + + return v != null && strategy.equalValues(v, e.getValue()); + } + + @Override public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + Object key = e.getKey(); + return key != null && Impl.this.remove(key, e.getValue()); + } + + @Override public int size() { + return Impl.this.size(); + } + + @Override public boolean isEmpty() { + return Impl.this.isEmpty(); + } + + @Override public void clear() { + Impl.this.clear(); + } + } + + /* ---------------- Serialization Support -------------- */ + + private static final long serialVersionUID = 1; + + private void writeObject(java.io.ObjectOutputStream out) + throws IOException { + out.writeInt(size()); + out.writeInt(segments.length); // concurrencyLevel + out.writeObject(strategy); + for (Entry entry : entrySet()) { + out.writeObject(entry.getKey()); + out.writeObject(entry.getValue()); + } + out.writeObject(null); // terminate entries + } + + /** + * Fields used during deserialization. We use a nested class so we don't + * load them until we need them. We need to use reflection to set final + * fields outside of the constructor. + */ + static class Fields { + + static final Field segmentShift = findField("segmentShift"); + static final Field segmentMask = findField("segmentMask"); + static final Field segments = findField("segments"); + static final Field strategy = findField("strategy"); + + static Field findField(String name) { + try { + Field f = Impl.class.getDeclaredField(name); + f.setAccessible(true); + return f; + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } + } + } + + @SuppressWarnings("unchecked") + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException { + try { + int initialCapacity = in.readInt(); + int concurrencyLevel = in.readInt(); + Strategy strategy = (Strategy) in.readObject(); + + if (concurrencyLevel > MAX_SEGMENTS) { + concurrencyLevel = MAX_SEGMENTS; + } + + // Find power-of-two sizes best matching arguments + int segmentShift = 0; + int segmentCount = 1; + while (segmentCount < concurrencyLevel) { + ++segmentShift; + segmentCount <<= 1; + } + Fields.segmentShift.set(this, 32 - segmentShift); + Fields.segmentMask.set(this, segmentCount - 1); + Fields.segments.set(this, newSegmentArray(segmentCount)); + + if (initialCapacity > MAXIMUM_CAPACITY) { + initialCapacity = MAXIMUM_CAPACITY; + } + + int segmentCapacity = initialCapacity / segmentCount; + if (segmentCapacity * segmentCount < initialCapacity) { + ++segmentCapacity; + } + + int segmentSize = 1; + while (segmentSize < segmentCapacity) { + segmentSize <<= 1; + } + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = new Segment(segmentSize); + } + + Fields.strategy.set(this, strategy); + + while (true) { + K key = (K) in.readObject(); + if (key == null) { + break; // terminator + } + V value = (V) in.readObject(); + put(key, value); + } + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + } + + static class ComputingImpl extends Impl { + + static final long serialVersionUID = 0; + + final ComputingStrategy computingStrategy; + final Function computer; + + /** + * Creates a new, empty map with the specified strategy, initial capacity, + * load factor and concurrency level. + */ + ComputingImpl(ComputingStrategy strategy, Builder builder, + Function computer) { + super(strategy, builder); + this.computingStrategy = strategy; + this.computer = computer; + } + + @Override public V get(Object k) { + /* + * This cast isn't safe, but we can rely on the fact that K is almost + * always passed to Map.get(), and tools like IDEs and Findbugs can + * catch situations where this isn't the case. + * + * The alternative is to add an overloaded method, but the chances of + * a user calling get() instead of the new API and the risks inherent + * in adding a new API outweigh this little hole. + */ + @SuppressWarnings("unchecked") + K key = (K) k; + + if (key == null) { + throw new NullPointerException("key"); + } + + int hash = hash(key); + Segment segment = segmentFor(hash); + outer: while (true) { + E entry = segment.getEntry(key, hash); + if (entry == null) { + boolean created = false; + segment.lock(); + try { + // Try again--an entry could have materialized in the interim. + entry = segment.getEntry(key, hash); + if (entry == null) { + // Create a new entry. + created = true; + int count = segment.count; + if (count++ > segment.threshold) { // ensure capacity + segment.expand(); + } + AtomicReferenceArray table = segment.table; + int index = hash & (table.length() - 1); + E first = table.get(index); + ++segment.modCount; + entry = computingStrategy.newEntry(key, hash, first); + table.set(index, entry); + segment.count = count; // write-volatile + } + } finally { + segment.unlock(); + } + + if (created) { + // This thread solely created the entry. + boolean success = false; + try { + V value = computingStrategy.compute(key, entry, computer); + if (value == null) { + throw new NullPointerException( + "compute() returned null unexpectedly"); + } + success = true; + return value; + } finally { + if (!success) { + segment.removeEntry(entry, hash); + } + } + } + } + + // The entry already exists. Wait for the computation. + boolean interrupted = false; + try { + while (true) { + try { + V value = computingStrategy.waitForValue(entry); + if (value == null) { + // Purge entry and try again. + segment.removeEntry(entry, hash); + continue outer; + } + return value; + } catch (InterruptedException e) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + } + } + + /** + * A basic, no-frills implementation of {@code Strategy} using {@link + * SimpleInternalEntry}. Intended to be subclassed to provide customized + * behavior. For example, the following creates a map that uses byte arrays as + * keys:

   {@code
+   *
+   *   return new CustomConcurrentHashMap.Builder().buildMap(
+   *       new SimpleStrategy() {
+   *         public int hashKey(Object key) {
+   *           return Arrays.hashCode((byte[]) key);
+   *         }
+   *         public boolean equalKeys(byte[] a, Object b) {
+   *           return Arrays.equals(a, (byte[]) b);
+   *         }
+   *       });}
+ * + * With nothing overridden, it yields map behavior equivalent to that of + * {@link ConcurrentHashMap}. + */ + static class SimpleStrategy + implements Strategy> { + public SimpleInternalEntry newEntry( + K key, int hash, SimpleInternalEntry next) { + return new SimpleInternalEntry(key, hash, null, next); + } + public SimpleInternalEntry copyEntry(K key, + SimpleInternalEntry original, SimpleInternalEntry next) { + return new SimpleInternalEntry( + key, original.hash, original.value, next); + } + public void setValue(SimpleInternalEntry entry, V value) { + entry.value = value; + } + public V getValue(SimpleInternalEntry entry) { + return entry.value; + } + public boolean equalKeys(K a, Object b) { + return a.equals(b); + } + public boolean equalValues(V a, Object b) { + return a.equals(b); + } + public int hashKey(Object key) { + return key.hashCode(); + } + public K getKey(SimpleInternalEntry entry) { + return entry.key; + } + public SimpleInternalEntry getNext(SimpleInternalEntry entry) { + return entry.next; + } + public int getHash(SimpleInternalEntry entry) { + return entry.hash; + } + public void setInternals( + Internals> internals) { + // ignore? + } + } + + /** + * A basic, no-frills entry class used by {@link SimpleInternalEntry}. + */ + static class SimpleInternalEntry { + final K key; + final int hash; + final SimpleInternalEntry next; + volatile V value; + SimpleInternalEntry( + K key, int hash, @Nullable V value, SimpleInternalEntry next) { + this.key = key; + this.hash = hash; + this.value = value; + this.next = next; + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableList.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableList.java new file mode 100644 index 00000000000..5a1678c4c1c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableList.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkElementIndex; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkPositionIndex; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkPositionIndexes; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; + +import javax.annotation.Nullable; + +/** + * An empty immutable list. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +final class EmptyImmutableList extends ImmutableList { + static final EmptyImmutableList INSTANCE = new EmptyImmutableList(); + + private EmptyImmutableList() {} + + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + @Override public Object[] toArray() { + return EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + public Object get(int index) { + // guaranteed to fail, but at least we get a consistent message + checkElementIndex(index, 0); + throw new AssertionError("unreachable"); + } + + @Override public int indexOf(Object target) { + return -1; + } + + @Override public int lastIndexOf(Object target) { + return -1; + } + + @Override public ImmutableList subList(int fromIndex, int toIndex) { + checkPositionIndexes(fromIndex, toIndex, 0); + return this; + } + + public ListIterator listIterator() { + return Collections.emptyList().listIterator(); + } + + public ListIterator listIterator(int start) { + checkPositionIndex(start, 0); + return Collections.emptyList().listIterator(); + } + + @Override public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof List) { + List that = (List) object; + return that.isEmpty(); + } + return false; + } + + @Override public int hashCode() { + return 1; + } + + @Override public String toString() { + return "[]"; + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableListMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableListMultimap.java new file mode 100644 index 00000000000..a34b5fa6303 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableListMultimap.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * Implementation of {@link ImmutableListMultimap} with no entries. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +class EmptyImmutableListMultimap extends ImmutableListMultimap { + static final EmptyImmutableListMultimap INSTANCE + = new EmptyImmutableListMultimap(); + + private EmptyImmutableListMultimap() { + super(ImmutableMap.>of(), 0); + } + + private Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableMap.java new file mode 100644 index 00000000000..602e7786322 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableMap.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An empty immutable map. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +final class EmptyImmutableMap extends ImmutableMap { + static final EmptyImmutableMap INSTANCE = new EmptyImmutableMap(); + + private EmptyImmutableMap() {} + + @Override public Object get(Object key) { + return null; + } + + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean containsKey(Object key) { + return false; + } + + @Override public boolean containsValue(Object value) { + return false; + } + + @Override public ImmutableSet> entrySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableSet keySet() { + return ImmutableSet.of(); + } + + @Override public ImmutableCollection values() { + return ImmutableCollection.EMPTY_IMMUTABLE_COLLECTION; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Map) { + Map that = (Map) object; + return that.isEmpty(); + } + return false; + } + + @Override public int hashCode() { + return 0; + } + + @Override public String toString() { + return "{}"; + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableMultiset.java new file mode 100644 index 00000000000..12d4822f3f0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableMultiset.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * An empty immutable multiset. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +final class EmptyImmutableMultiset extends ImmutableMultiset { + static final EmptyImmutableMultiset INSTANCE = new EmptyImmutableMultiset(); + + private EmptyImmutableMultiset() { + super(ImmutableMap.of(), 0); + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSet.java new file mode 100644 index 00000000000..f42dd630d30 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSet.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An empty immutable set. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +final class EmptyImmutableSet extends ImmutableSet { + static final EmptyImmutableSet INSTANCE = new EmptyImmutableSet(); + + private EmptyImmutableSet() {} + + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + @Override public Object[] toArray() { + return EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + @Override public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Set) { + Set that = (Set) object; + return that.isEmpty(); + } + return false; + } + + @Override public final int hashCode() { + return 0; + } + + @Override boolean isHashCodeFast() { + return true; + } + + @Override public String toString() { + return "[]"; + } + + Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSetMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSetMultimap.java new file mode 100644 index 00000000000..96f8aaac1c6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSetMultimap.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * Implementation of {@link ImmutableListMultimap} with no entries. + * + * @author Mike Ward + */ +@GwtCompatible(serializable = true) +class EmptyImmutableSetMultimap extends ImmutableSetMultimap { + static final EmptyImmutableSetMultimap INSTANCE + = new EmptyImmutableSetMultimap(); + + private EmptyImmutableSetMultimap() { + super(ImmutableMap.>of(), 0); + } + + private Object readResolve() { + return INSTANCE; // preserve singleton property + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSortedSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSortedSet.java new file mode 100644 index 00000000000..5bbb48df9c8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EmptyImmutableSortedSet.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An empty immutable sorted set. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class EmptyImmutableSortedSet extends ImmutableSortedSet { + EmptyImmutableSortedSet(Comparator comparator) { + super(comparator); + } + + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(Object target) { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.emptyIterator(); + } + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + @Override public Object[] toArray() { + return EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + @Override public boolean containsAll(Collection targets) { + return targets.isEmpty(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Set) { + Set that = (Set) object; + return that.isEmpty(); + } + return false; + } + + @Override public int hashCode() { + return 0; + } + + @Override public String toString() { + return "[]"; + } + + public E first() { + throw new NoSuchElementException(); + } + + public E last() { + throw new NoSuchElementException(); + } + + @Override ImmutableSortedSet headSetImpl(E toElement) { + return this; + } + + @Override ImmutableSortedSet subSetImpl(E fromElement, E toElement) { + return this; + } + + @Override ImmutableSortedSet tailSetImpl(E fromElement) { + return this; + } + + @Override boolean hasPartialArray() { + return false; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumBiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumBiMap.java new file mode 100644 index 00000000000..7bf2a8fd7bf --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumBiMap.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Map; + +/** + * A {@code BiMap} backed by two {@code EnumMap} instances. Null keys and values + * are not permitted. An {@code EnumBiMap} and its inverse are both + * serializable. + * + * @author Mike Bostock + */ +public final class EnumBiMap, V extends Enum> + extends AbstractBiMap { + private transient Class keyType; + private transient Class valueType; + + /** + * Returns a new, empty {@code EnumBiMap} using the specified key and value + * types. + * + * @param keyType the key type + * @param valueType the value type + */ + public static , V extends Enum> EnumBiMap + create(Class keyType, Class valueType) { + return new EnumBiMap(keyType, valueType); + } + + /** + * Returns a new bimap with the same mappings as the specified map. If the + * specified map is an {@code EnumBiMap}, the new bimap has the same types as + * the provided map. Otherwise, the specified map must contain at least one + * mapping, in order to determine the key and value types. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} + * instance and contains no mappings + */ + public static , V extends Enum> EnumBiMap + create(Map map) { + EnumBiMap bimap = create(inferKeyType(map), inferValueType(map)); + bimap.putAll(map); + return bimap; + } + + private EnumBiMap(Class keyType, Class valueType) { + super(new EnumMap(keyType), new EnumMap(valueType)); + this.keyType = keyType; + this.valueType = valueType; + } + + static > Class inferKeyType(Map map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap) map).keyType(); + } + if (map instanceof EnumHashBiMap) { + return ((EnumHashBiMap) map).keyType(); + } + checkArgument(!map.isEmpty()); + return map.keySet().iterator().next().getDeclaringClass(); + } + + private static > Class inferValueType(Map map) { + if (map instanceof EnumBiMap) { + return ((EnumBiMap) map).valueType; + } + checkArgument(!map.isEmpty()); + return map.values().iterator().next().getDeclaringClass(); + } + + /** Returns the associated key type. */ + public Class keyType() { + return keyType; + } + + /** Returns the associated value type. */ + public Class valueType() { + return valueType; + } + + /** + * @serialData the key class, value class, number of entries, first key, first + * value, second key, second value, and so on. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + stream.writeObject(valueType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading fields populated by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class) stream.readObject(); + valueType = (Class) stream.readObject(); + setDelegates(new EnumMap(keyType), new EnumMap(valueType)); + Serialization.populateMap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumHashBiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumHashBiMap.java new file mode 100644 index 00000000000..3f970eb153d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumHashBiMap.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A {@code BiMap} backed by an {@code EnumMap} instance for keys-to-values, and + * a {@code HashMap} instance for values-to-keys. Null keys are not permitted, + * but null values are. An {@code EnumHashBiMap} and its inverse are both + * serializable. + * + * @author Mike Bostock + */ +public final class EnumHashBiMap, V> + extends AbstractBiMap { + private transient Class keyType; + + /** + * Returns a new, empty {@code EnumHashBiMap} using the specified key type. + * + * @param keyType the key type + */ + public static , V> EnumHashBiMap + create(Class keyType) { + return new EnumHashBiMap(keyType); + } + + /** + * Constructs a new bimap with the same mappings as the specified map. If the + * specified map is an {@code EnumHashBiMap} or an {@link EnumBiMap}, the new + * bimap has the same key type as the input bimap. Otherwise, the specified + * map must contain at least one mapping, in order to determine the key type. + * + * @param map the map whose mappings are to be placed in this map + * @throws IllegalArgumentException if map is not an {@code EnumBiMap} or an + * {@code EnumHashBiMap} instance and contains no mappings + */ + public static , V> EnumHashBiMap + create(Map map) { + EnumHashBiMap bimap = create(EnumBiMap.inferKeyType(map)); + bimap.putAll(map); + return bimap; + } + + private EnumHashBiMap(Class keyType) { + super(new EnumMap(keyType), Maps.newHashMapWithExpectedSize( + keyType.getEnumConstants().length)); + this.keyType = keyType; + } + + // Overriding these two methods to show that values may be null (but not keys) + + @Override public V put(K key, @Nullable V value) { + return super.put(key, value); + } + + @Override public V forcePut(K key, @Nullable V value) { + return super.forcePut(key, value); + } + + /** Returns the associated key type. */ + public Class keyType() { + return keyType; + } + + /** + * @serialData the key class, number of entries, first key, first value, + * second key, second value, and so on. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyType); + Serialization.writeMap(this, stream); + } + + @SuppressWarnings("unchecked") // reading field populated by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyType = (Class) stream.readObject(); + setDelegates(new EnumMap(keyType), + new HashMap(keyType.getEnumConstants().length * 3 / 2)); + Serialization.populateMap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumMultiset.java new file mode 100644 index 00000000000..577dacff3d5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/EnumMultiset.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Multiset implementation backed by an {@link EnumMap}. + * + * @author Jared Levy + */ +@GwtCompatible +public final class EnumMultiset> + extends AbstractMapBasedMultiset { + /** Creates an empty {@code EnumMultiset}. */ + public static > EnumMultiset create(Class type) { + return new EnumMultiset(type); + } + + /** + * Creates a new {@code EnumMultiset} containing the specified elements. + * + * @param elements the elements that the multiset should contain + * @throws IllegalArgumentException if {@code elements} is empty + */ + public static > EnumMultiset create( + Iterable elements) { + Iterator iterator = elements.iterator(); + checkArgument(iterator.hasNext(), + "EnumMultiset constructor passed empty Iterable"); + EnumMultiset multiset + = new EnumMultiset(iterator.next().getDeclaringClass()); + Iterables.addAll(multiset, elements); + return multiset; + } + + private transient Class type; + + /** Creates an empty {@code EnumMultiset}. */ + private EnumMultiset(Class type) { + super(new EnumMap(type)); + this.type = type; + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(type); + Serialization.writeMultiset(this, stream); + } + + /** + * @serialData the {@code Class} for the enum type, the number of distinct + * elements, the first element, its count, the second element, its count, + * and so on + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + Class localType = (Class) stream.readObject(); + type = localType; + setBackingMap(new EnumMap(type)); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ExpirationTimer.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ExpirationTimer.java new file mode 100644 index 00000000000..4c194562d06 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ExpirationTimer.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import java.util.Timer; + +/** + * Timer used for entry expiration in MapMaker. + */ +class ExpirationTimer { + static Timer instance = new Timer(true); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ExplicitOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ExplicitOrdering.java new file mode 100644 index 00000000000..11bd621f110 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ExplicitOrdering.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.List; + +import javax.annotation.Nullable; + +/** An ordering that compares objects according to a given order. */ +@GwtCompatible(serializable = true) +final class ExplicitOrdering extends Ordering implements Serializable { + final ImmutableMap rankMap; + + ExplicitOrdering(List valuesInOrder) { + this(buildRankMap(valuesInOrder)); + } + + ExplicitOrdering(ImmutableMap rankMap) { + this.rankMap = rankMap; + } + + public int compare(T left, T right) { + return rank(left) - rank(right); // safe because both are nonnegative + } + + private int rank(T value) { + Integer rank = rankMap.get(value); + if (rank == null) { + throw new IncomparableValueException(value); + } + return rank; + } + + private static ImmutableMap buildRankMap( + List valuesInOrder) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + int rank = 0; + for (T value : valuesInOrder) { + builder.put(value, rank++); + } + return builder.build(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof ExplicitOrdering) { + ExplicitOrdering that = (ExplicitOrdering) object; + return this.rankMap.equals(that.rankMap); + } + return false; + } + + @Override public int hashCode() { + return rankMap.hashCode(); + } + + @Override public String toString() { + return "Ordering.explicit(" + rankMap.keySet() + ")"; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingCollection.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingCollection.java new file mode 100644 index 00000000000..c77d279badb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingCollection.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A collection which forwards all its method calls to another collection. + * Subclasses should override one or more methods to modify the behavior of + * the backing collection as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +@GwtCompatible +public abstract class ForwardingCollection extends ForwardingObject + implements Collection { + + @Override protected abstract Collection delegate(); + + public Iterator iterator() { + return delegate().iterator(); + } + + public int size() { + return delegate().size(); + } + + public boolean removeAll(Collection collection) { + return delegate().removeAll(collection); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public boolean contains(Object object) { + return delegate().contains(object); + } + + public Object[] toArray() { + return delegate().toArray(); + } + + public T[] toArray(T[] array) { + return delegate().toArray(array); + } + + public boolean add(E element) { + return delegate().add(element); + } + + public boolean remove(Object object) { + return delegate().remove(object); + } + + public boolean containsAll(Collection collection) { + return delegate().containsAll(collection); + } + + public boolean addAll(Collection collection) { + return delegate().addAll(collection); + } + + public boolean retainAll(Collection collection) { + return delegate().retainAll(collection); + } + + public void clear() { + delegate().clear(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingConcurrentMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingConcurrentMap.java new file mode 100644 index 00000000000..ca6c71b4067 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingConcurrentMap.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.concurrent.ConcurrentMap; + +/** + * A concurrent map which forwards all its method calls to another concurrent + * map. Subclasses should override one or more methods to modify the behavior of + * the backing map as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Charles Fry + */ +@GwtCompatible +public abstract class ForwardingConcurrentMap extends ForwardingMap + implements ConcurrentMap { + + @Override protected abstract ConcurrentMap delegate(); + + public V putIfAbsent(K key, V value) { + return delegate().putIfAbsent(key, value); + } + + public boolean remove(Object key, Object value) { + return delegate().remove(key, value); + } + + public V replace(K key, V value) { + return delegate().replace(key, value); + } + + public boolean replace(K key, V oldValue, V newValue) { + return delegate().replace(key, oldValue, newValue); + } + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingIterator.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingIterator.java new file mode 100644 index 00000000000..401982c42ed --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingIterator.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Iterator; + +/** + * An iterator which forwards all its method calls to another iterator. + * Subclasses should override one or more methods to modify the behavior of the + * backing iterator as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +@GwtCompatible +public abstract class ForwardingIterator + extends ForwardingObject implements Iterator { + + @Override protected abstract Iterator delegate(); + + public boolean hasNext() { + return delegate().hasNext(); + } + + public T next() { + return delegate().next(); + } + + public void remove() { + delegate().remove(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingList.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingList.java new file mode 100644 index 00000000000..27f6a874dc5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingList.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; + +import javax.annotation.Nullable; + +/** + * A list which forwards all its method calls to another list. Subclasses should + * override one or more methods to modify the behavior of the backing list as + * desired per the decorator pattern. + * + *

This class does not implement {@link java.util.RandomAccess}. If the + * delegate supports random access, the {@code ForwadingList} subclass should + * implement the {@code RandomAccess} interface. + * + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingList extends ForwardingCollection + implements List { + + @Override protected abstract List delegate(); + + public void add(int index, E element) { + delegate().add(index, element); + } + + public boolean addAll(int index, Collection elements) { + return delegate().addAll(index, elements); + } + + public E get(int index) { + return delegate().get(index); + } + + public int indexOf(Object element) { + return delegate().indexOf(element); + } + + public int lastIndexOf(Object element) { + return delegate().lastIndexOf(element); + } + + public ListIterator listIterator() { + return delegate().listIterator(); + } + + public ListIterator listIterator(int index) { + return delegate().listIterator(index); + } + + public E remove(int index) { + return delegate().remove(index); + } + + public E set(int index, E element) { + return delegate().set(index, element); + } + + @GwtIncompatible("List.subList") + public List subList(int fromIndex, int toIndex) { + return Platform.subList(delegate(), fromIndex, toIndex); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingListIterator.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingListIterator.java new file mode 100644 index 00000000000..21ff32ed48f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingListIterator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.ListIterator; + +/** + * A list iterator which forwards all its method calls to another list + * iterator. Subclasses should override one or more methods to modify the + * behavior of the backing iterator as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingListIterator extends ForwardingIterator + implements ListIterator { + + @Override protected abstract ListIterator delegate(); + + public void add(E element) { + delegate().add(element); + } + + public boolean hasPrevious() { + return delegate().hasPrevious(); + } + + public int nextIndex() { + return delegate().nextIndex(); + } + + public E previous() { + return delegate().previous(); + } + + public int previousIndex() { + return delegate().previousIndex(); + } + + public void set(E element) { + delegate().set(element); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMap.java new file mode 100644 index 00000000000..ca824042254 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMap.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A map which forwards all its method calls to another map. Subclasses should + * override one or more methods to modify the behavior of the backing map as + * desired per the decorator pattern. + * + * @see ForwardingObject + * @author Kevin Bourrillion + * @author Jared Levy + */ +@GwtCompatible +public abstract class ForwardingMap extends ForwardingObject + implements Map { + + @Override protected abstract Map delegate(); + + public int size() { + return delegate().size(); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public V remove(Object object) { + return delegate().remove(object); + } + + public void clear() { + delegate().clear(); + } + + public boolean containsKey(Object key) { + return delegate().containsKey(key); + } + + public boolean containsValue(Object value) { + return delegate().containsValue(value); + } + + public V get(Object key) { + return delegate().get(key); + } + + public V put(K key, V value) { + return delegate().put(key, value); + } + + public void putAll(Map map) { + delegate().putAll(map); + } + + public Set keySet() { + return delegate().keySet(); + } + + public Collection values() { + return delegate().values(); + } + + public Set> entrySet() { + return delegate().entrySet(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMapEntry.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMapEntry.java new file mode 100644 index 00000000000..8ceac453ca1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMapEntry.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A map entry which forwards all its method calls to another map entry. + * Subclasses should override one or more methods to modify the behavior of the + * backing map entry as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingMapEntry + extends ForwardingObject implements Map.Entry { + + @Override protected abstract Map.Entry delegate(); + + public K getKey() { + return delegate().getKey(); + } + + public V getValue() { + return delegate().getValue(); + } + + public V setValue(V value) { + return delegate().setValue(value); + } + + @Override public boolean equals(@Nullable Object object) { + return delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMultimap.java new file mode 100644 index 00000000000..23ccfd96af0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMultimap.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A multimap which forwards all its method calls to another multimap. + * Subclasses should override one or more methods to modify the behavior of + * the backing multimap as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Robert Konigsberg + */ +@GwtCompatible +public abstract class ForwardingMultimap extends ForwardingObject + implements Multimap { + + @Override protected abstract Multimap delegate(); + + public Map> asMap() { + return delegate().asMap(); + } + + public void clear() { + delegate().clear(); + } + + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + return delegate().containsEntry(key, value); + } + + public boolean containsKey(@Nullable Object key) { + return delegate().containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + return delegate().containsValue(value); + } + + public Collection> entries() { + return delegate().entries(); + } + + public Collection get(@Nullable K key) { + return delegate().get(key); + } + + public boolean isEmpty() { + return delegate().isEmpty(); + } + + public Multiset keys() { + return delegate().keys(); + } + + public Set keySet() { + return delegate().keySet(); + } + + public boolean put(K key, V value) { + return delegate().put(key, value); + } + + public boolean putAll(K key, Iterable values) { + return delegate().putAll(key, values); + } + + public boolean putAll(Multimap multimap) { + return delegate().putAll(multimap); + } + + public boolean remove(@Nullable Object key, @Nullable Object value) { + return delegate().remove(key, value); + } + + public Collection removeAll(@Nullable Object key) { + return delegate().removeAll(key); + } + + public Collection replaceValues(K key, Iterable values) { + return delegate().replaceValues(key, values); + } + + public int size() { + return delegate().size(); + } + + public Collection values() { + return delegate().values(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMultiset.java new file mode 100644 index 00000000000..33778e889ac --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingMultiset.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A multiset which forwards all its method calls to another multiset. + * Subclasses should override one or more methods to modify the behavior of the + * backing multiset as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +@GwtCompatible +public abstract class ForwardingMultiset extends ForwardingCollection + implements Multiset { + + @Override protected abstract Multiset delegate(); + + public int count(Object element) { + return delegate().count(element); + } + + public int add(E element, int occurrences) { + return delegate().add(element, occurrences); + } + + public int remove(Object element, int occurrences) { + return delegate().remove(element, occurrences); + } + + public Set elementSet() { + return delegate().elementSet(); + } + + public Set> entrySet() { + return delegate().entrySet(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + public int setCount(E element, int count) { + return delegate().setCount(element, count); + } + + public boolean setCount(E element, int oldCount, int newCount) { + return delegate().setCount(element, oldCount, newCount); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingObject.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingObject.java new file mode 100644 index 00000000000..294f545c2f0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingObject.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; + +/** + * An abstract base class for implementing the decorator pattern. + * The {@link #delegate()} method must be overridden to return the instance + * being decorated. + * + * This class does not forward the {@code hashCode} and {@code equals} + * methods through to the backing object, but relies on {@code Object}'s + * implementation. This is necessary to preserve the symmetry of {@code equals}. + * Custom definitions of equality are usually based on an interface, such as + * {@code Set} or {@code List}, so that the implementation of {@code equals} can + * cast the object being tested for equality to the custom interface. {@code + * ForwardingObject} implements no such custom interfaces directly; they + * are implemented only in subclasses. Therefore, forwarding {@code equals} + * would break symmetry, as the forwarding object might consider itself equal to + * the object being tested, but the reverse could not be true. This behavior is + * consistent with the JDK's collection wrappers, such as + * {@link java.util.Collections#unmodifiableCollection}. Use an + * interface-specific subclass of {@code ForwardingObject}, such as {@link + * ForwardingList}, to preserve equality behavior, or override {@code equals} + * directly. + * + *

The {@code toString} method is forwarded to the delegate. Although this + * class does not implement {@link Serializable}, a serializable subclass may be + * created since this class has a parameter-less constructor. + * + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingObject { + + /** Sole constructor. */ + protected ForwardingObject() {} + + /** + * Returns the backing delegate instance that methods are forwarded to. + * Abstract subclasses generally override this method with an abstract method + * that has a more specific return type, such as {@link + * ForwardingSet#delegate}. Concrete subclasses override this method to supply + * the instance being decorated. + */ + protected abstract Object delegate(); + + /** + * Returns the string representation generated by the delegate's + * {@code toString} method. + */ + @Override public String toString() { + return delegate().toString(); + } + + /* No equals or hashCode. See class comments for details. */ +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingQueue.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingQueue.java new file mode 100644 index 00000000000..1357b7bf800 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingQueue.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Queue; + +/** + * A queue which forwards all its method calls to another queue. Subclasses + * should override one or more methods to modify the behavior of the backing + * queue as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingQueue extends ForwardingCollection + implements Queue { + + @Override protected abstract Queue delegate(); + + public boolean offer(E o) { + return delegate().offer(o); + } + + public E poll() { + return delegate().poll(); + } + + public E remove() { + return delegate().remove(); + } + + public E peek() { + return delegate().peek(); + } + + public E element() { + return delegate().element(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSet.java new file mode 100644 index 00000000000..2ff2b5eaa9d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSet.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A set which forwards all its method calls to another set. Subclasses should + * override one or more methods to modify the behavior of the backing set as + * desired per the decorator pattern. + * + * @see ForwardingObject + * @author Kevin Bourrillion + */ +@GwtCompatible +public abstract class ForwardingSet extends ForwardingCollection + implements Set { + + @Override protected abstract Set delegate(); + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSortedMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSortedMap.java new file mode 100644 index 00000000000..30568f75449 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSortedMap.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.SortedMap; + +/** + * A sorted map which forwards all its method calls to another sorted map. + * Subclasses should override one or more methods to modify the behavior of + * the backing sorted map as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingSortedMap extends ForwardingMap + implements SortedMap { + + @Override protected abstract SortedMap delegate(); + + public Comparator comparator() { + return delegate().comparator(); + } + + public K firstKey() { + return delegate().firstKey(); + } + + public SortedMap headMap(K toKey) { + return delegate().headMap(toKey); + } + + public K lastKey() { + return delegate().lastKey(); + } + + public SortedMap subMap(K fromKey, K toKey) { + return delegate().subMap(fromKey, toKey); + } + + public SortedMap tailMap(K fromKey) { + return delegate().tailMap(fromKey); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSortedSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSortedSet.java new file mode 100644 index 00000000000..85b0238a64a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ForwardingSortedSet.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Comparator; +import java.util.SortedSet; + +/** + * A sorted set which forwards all its method calls to another sorted set. + * Subclasses should override one or more methods to modify the behavior of the + * backing sorted set as desired per the decorator pattern. + * + * @see ForwardingObject + * @author Mike Bostock + */ +@GwtCompatible +public abstract class ForwardingSortedSet extends ForwardingSet + implements SortedSet { + + @Override protected abstract SortedSet delegate(); + + public Comparator comparator() { + return delegate().comparator(); + } + + public E first() { + return delegate().first(); + } + + public SortedSet headSet(E toElement) { + return delegate().headSet(toElement); + } + + public E last() { + return delegate().last(); + } + + public SortedSet subSet(E fromElement, E toElement) { + return delegate().subSet(fromElement, toElement); + } + + public SortedSet tailSet(E fromElement) { + return delegate().tailSet(fromElement); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashBiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashBiMap.java new file mode 100644 index 00000000000..f125224f7f6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashBiMap.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A {@link BiMap} backed by two {@link HashMap} instances. This implementation + * allows null keys and values. A {@code HashBiMap} and its inverse are both + * serializable. + * + * @author Mike Bostock + */ +@GwtCompatible +public final class HashBiMap extends AbstractBiMap { + + /** + * Returns a new, empty {@code HashBiMap} with the default initial capacity + * (16). + */ + public static HashBiMap create() { + return new HashBiMap(); + } + + /** + * Constructs a new, empty bimap with the specified expected size. + * + * @param expectedSize the expected number of entries + * @throws IllegalArgumentException if the specified expected size is + * negative + */ + public static HashBiMap create(int expectedSize) { + return new HashBiMap(expectedSize); + } + + /** + * Constructs a new bimap containing initial values from {@code map}. The + * bimap is created with an initial capacity sufficient to hold the mappings + * in the specified map. + */ + public static HashBiMap create( + Map map) { + HashBiMap bimap = create(map.size()); + bimap.putAll(map); + return bimap; + } + + private HashBiMap() { + super(new HashMap(), new HashMap()); + } + + private HashBiMap(int expectedSize) { + super(new HashMap(Maps.capacity(expectedSize)), + new HashMap(Maps.capacity(expectedSize))); + } + + // Override these two methods to show that keys and values may be null + + @Override public V put(@Nullable K key, @Nullable V value) { + return super.put(key, value); + } + + @Override public V forcePut(@Nullable K key, @Nullable V value) { + return super.forcePut(key, value); + } + + /** + * @serialData the number of entries, first key, first value, second key, + * second value, and so on. + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int size = Serialization.readCount(stream); + setDelegates(Maps.newHashMapWithExpectedSize(size), + Maps.newHashMapWithExpectedSize(size)); + Serialization.populateMap(this, stream, size); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashMultimap.java new file mode 100644 index 00000000000..25b361aefbd --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashMultimap.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import org.elasticsearch.util.gcommon.base.Preconditions; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of {@link Multimap} using hash tables. + * + *

The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSetMultimap}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public final class HashMultimap extends AbstractSetMultimap { + private static final int DEFAULT_VALUES_PER_KEY = 8; + + @VisibleForTesting + transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + + /** + * Creates a new, empty {@code HashMultimap} with the default initial + * capacities. + */ + public static HashMultimap create() { + return new HashMultimap(); + } + + /** + * Constructs an empty {@code HashMultimap} with enough capacity to hold the + * specified numbers of keys and values without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code + * expectedValuesPerKey} is negative + */ + public static HashMultimap create( + int expectedKeys, int expectedValuesPerKey) { + return new HashMultimap(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs a {@code HashMultimap} with the same mappings as the specified + * multimap. If a key-value mapping appears multiple times in the input + * multimap, it only appears once in the constructed multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static HashMultimap create( + Multimap multimap) { + return new HashMultimap(multimap); + } + + private HashMultimap() { + super(new HashMap>()); + } + + private HashMultimap(int expectedKeys, int expectedValuesPerKey) { + super(Maps.>newHashMapWithExpectedSize(expectedKeys)); + Preconditions.checkArgument(expectedValuesPerKey >= 0); + this.expectedValuesPerKey = expectedValuesPerKey; + } + + private HashMultimap(Multimap multimap) { + super(Maps.>newHashMapWithExpectedSize( + multimap.keySet().size())); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code HashSet} for a collection of values for one key. + * + * @return a new {@code HashSet} containing a collection of values for one key + */ + @Override Set createCollection() { + return Sets.newHashSetWithExpectedSize(expectedValuesPerKey); + } + + /** + * @serialData expectedValuesPerKey, number of distinct keys, and then for + * each distinct key: the key, number of values for that key, and the + * key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(expectedValuesPerKey); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = stream.readInt(); + int distinctKeys = Serialization.readCount(stream); + Map> map = Maps.newHashMapWithExpectedSize(distinctKeys); + setMap(map); + Serialization.populateMultimap(this, stream, distinctKeys); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashMultiset.java new file mode 100644 index 00000000000..4c76dcebf49 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/HashMultiset.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Multiset implementation backed by a {@link HashMap}. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public final class HashMultiset extends AbstractMapBasedMultiset { + + /** + * Creates a new, empty {@code HashMultiset} using the default initial + * capacity. + */ + public static HashMultiset create() { + return new HashMultiset(); + } + + /** + * Creates a new, empty {@code HashMultiset} with the specified expected + * number of distinct elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static HashMultiset create(int distinctElements) { + return new HashMultiset(distinctElements); + } + + /** + * Creates a new {@code HashMultiset} containing the specified elements. + * + * @param elements the elements that the multiset should contain + */ + public static HashMultiset create(Iterable elements) { + HashMultiset multiset = + create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private HashMultiset() { + super(new HashMap()); + } + + private HashMultiset(int distinctElements) { + super(new HashMap(Maps.capacity(distinctElements))); + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int distinctElements = Serialization.readCount(stream); + setBackingMap( + Maps.newHashMapWithExpectedSize(distinctElements)); + Serialization.populateMultiset(this, stream, distinctElements); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Hashing.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Hashing.java new file mode 100644 index 00000000000..2145e6b8f0d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Hashing.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; + +/** + * Static methods for implementing hash-based collections. + * + * @author Kevin Bourrillion + * @author Jesse Wilson + */ +@GwtCompatible +final class Hashing { + private Hashing() {} + + /* + * This method was written by Doug Lea with assistance from members of JCP + * JSR-166 Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ + static int smear(int hashCode) { + hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12); + return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4); + } + + // We use power-of-2 tables, and this is the highest int that's a power of 2 + private static final int MAX_TABLE_SIZE = 1 << 30; + + // If the set has this many elements, it will "max out" the table size + private static final int CUTOFF = 1 << 29; + + // Size the table to be at most 50% full, if possible + static int chooseTableSize(int setSize) { + if (setSize < CUTOFF) { + return Integer.highestOneBit(setSize) << 2; + } + + // The table can't be completely full or we'll get infinite reprobes + checkArgument(setSize < MAX_TABLE_SIZE, "collection too large"); + return MAX_TABLE_SIZE; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableBiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableBiMap.java new file mode 100644 index 00000000000..6fdb64d11bb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableBiMap.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable {@link BiMap} with reliable user-specified iteration order. Does + * not permit null keys or values. An {@code ImmutableBiMap} and its inverse + * have the same iteration ordering. + * + *

An instance of {@code ImmutableBiMap} contains its own data and will + * never change. {@code ImmutableBiMap} is convenient for + * {@code public static final} maps ("constant maps") and also lets you easily + * make a "defensive copy" of a bimap provided to your class by a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public abstract class ImmutableBiMap extends ImmutableMap + implements BiMap { + + private static final ImmutableBiMap EMPTY_IMMUTABLE_BIMAP + = new EmptyBiMap(); + + /** + * Returns the empty bimap. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableBiMap of() { + return (ImmutableBiMap) EMPTY_IMMUTABLE_BIMAP; + } + + /** + * Returns an immutable bimap containing a single entry. + */ + public static ImmutableBiMap of(K k1, V v1) { + return new RegularImmutableBiMap(ImmutableMap.of(k1, v1)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of(K k1, V v1, K k2, V v2) { + return new RegularImmutableBiMap(ImmutableMap.of(k1, v1, k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return new RegularImmutableBiMap(ImmutableMap.of( + k1, v1, k2, v2, k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new RegularImmutableBiMap(ImmutableMap.of( + k1, v1, k2, v2, k3, v3, k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys or values are added + */ + public static ImmutableBiMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new RegularImmutableBiMap(ImmutableMap.of( + k1, v1, k2, v2, k3, v3, k4, v4, k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable bimap instances, especially {@code public + * static final} bimaps ("constant bimaps"). Example:

   {@code
+   *
+   *   static final ImmutableBiMap WORD_TO_INT =
+   *       new ImmutableBiMap.Builder()
+   *           .put("one", 1)
+   *           .put("two", 2)
+   *           .put("three", 3)
+   *           .build();}
+ * + * For small immutable bimaps, the {@code ImmutableBiMap.of()} methods + * are even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple bimaps in series. Each bimap is a superset + * of the bimaps created before it. + */ + public static final class Builder extends ImmutableMap.Builder { + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableBiMap#builder}. + */ + public Builder() {} + + /** + * Associates {@code key} with {@code value} in the built bimap. Duplicate + * keys or values are not allowed, and will cause {@link #build} to fail. + */ + @Override public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Associates all of the given map's keys and values in the built bimap. + * Duplicate keys or values are not allowed, and will cause {@link #build} + * to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override public Builder putAll(Map map) { + super.putAll(map); + return this; + } + + /** + * Returns a newly-created immutable bimap. + * + * @throws IllegalArgumentException if duplicate keys or values were added + */ + @Override public ImmutableBiMap build() { + ImmutableMap map = super.build(); + if (map.isEmpty()) { + return of(); + } + return new RegularImmutableBiMap(super.build()); + } + } + + /** + * Returns an immutable bimap containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not consistent with + * equals), the results of this method are undefined. + * + *

Note: If {@code map} is an {@code ImmutableBiMap}, the given map + * itself will be returned. + * + * @throws IllegalArgumentException if two keys have the same value + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableBiMap copyOf( + Map map) { + if (map instanceof ImmutableBiMap) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableBiMap bimap = (ImmutableBiMap) map; + return bimap; + } + + if (map.isEmpty()) { + return of(); + } + + ImmutableMap immutableMap = ImmutableMap.copyOf(map); + return new RegularImmutableBiMap(immutableMap); + } + + ImmutableBiMap() {} + + abstract ImmutableMap delegate(); + + /** + * {@inheritDoc} + * + *

The inverse of an {@code ImmutableBiMap} is another + * {@code ImmutableBiMap}. + */ + public abstract ImmutableBiMap inverse(); + + @Override public boolean containsKey(@Nullable Object key) { + return delegate().containsKey(key); + } + + @Override public boolean containsValue(@Nullable Object value) { + return inverse().containsKey(value); + } + + @Override public ImmutableSet> entrySet() { + return delegate().entrySet(); + } + + @Override public V get(@Nullable Object key) { + return delegate().get(key); + } + + @Override public ImmutableSet keySet() { + return delegate().keySet(); + } + + /** + * Returns an immutable set of the values in this map. The values are in the + * same order as the parameters used to build this map. + */ + @Override public ImmutableSet values() { + return inverse().keySet(); + } + + /** + * Guaranteed to throw an exception and leave the bimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isEmpty() { + return delegate().isEmpty(); + } + + public int size() { + return delegate().size(); + } + + @Override public boolean equals(@Nullable Object object) { + return object == this || delegate().equals(object); + } + + @Override public int hashCode() { + return delegate().hashCode(); + } + + @Override public String toString() { + return delegate().toString(); + } + + /** Bimap with no mappings. */ + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + static class EmptyBiMap extends ImmutableBiMap { + @Override ImmutableMap delegate() { + return ImmutableMap.of(); + } + @Override public ImmutableBiMap inverse() { + return this; + } + Object readResolve() { + return EMPTY_IMMUTABLE_BIMAP; // preserve singleton property + } + } + + /** + * Serialized type for all ImmutableBiMap instances. It captures the logical + * contents and they are reconstructed using public factory methods. This + * ensures that the implementation types remain as implementation details. + * + * Since the bimap is immutable, ImmutableBiMap doesn't require special logic + * for keeping the bimap and its inverse in sync during serialization, the way + * AbstractBiMap does. + */ + private static class SerializedForm extends ImmutableMap.SerializedForm { + SerializedForm(ImmutableBiMap bimap) { + super(bimap); + } + @Override Object readResolve() { + Builder builder = new Builder(); + return createMap(builder); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableClassToInstanceMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableClassToInstanceMap.java new file mode 100644 index 00000000000..818f782ccc1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableClassToInstanceMap.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import static org.elasticsearch.util.gcommon.collect.MutableClassToInstanceMap.cast; + +import java.util.Map; + +/** + * A class-to-instance map backed by an {@link ImmutableMap}. See also {@link + * MutableClassToInstanceMap}. + * + * @author Kevin Bourrillion + */ +public final class ImmutableClassToInstanceMap extends + ForwardingMap, B> implements ClassToInstanceMap { + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable class-to-instance maps. Example: + *

   {@code
+   *
+   *   static final ImmutableClassToInstanceMap HANDLERS =
+   *       new ImmutableClassToInstanceMap.Builder()
+   *           .put(FooHandler.class, new FooHandler())
+   *           .put(BarHandler.class, new SubBarHandler())
+   *           .put(Handler.class, new QuuxHandler())
+   *           .build();}
+ * + *

After invoking {@link #build()} it is still possible to add more + * entries and build again. Thus each map generated by this builder will be + * a superset of any map generated before it. + */ + public static final class Builder { + private final ImmutableMap.Builder, B> mapBuilder + = ImmutableMap.builder(); + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder put(Class type, T value) { + mapBuilder.put(type, value); + return this; + } + + /** + * Associates all of {@code map's} keys and values in the built map. + * Duplicate keys are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws ClassCastException if any value is not an instance of the type + * specified by its key + */ + public Builder putAll( + Map, ? extends T> map) { + for (Entry, ? extends T> entry + : map.entrySet()) { + Class type = entry.getKey(); + T value = entry.getValue(); + mapBuilder.put(type, cast(type, value)); + } + return this; + } + + /** + * Returns a new immutable class-to-instance map containing the entries + * provided to this builder. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableClassToInstanceMap build() { + return new ImmutableClassToInstanceMap(mapBuilder.build()); + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not consistent with + * equals), the results of this method are undefined. + * + *

Note: Despite what the method name suggests, if {@code map} is + * an {@code ImmutableClassToInstanceMap}, no copy will actually be performed. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws ClassCastException if any value is not an instance of the type + * specified by its key + */ + @SuppressWarnings("unchecked") // covariant casts safe (unmodifiable) + public static ImmutableClassToInstanceMap copyOf( + Map, ? extends S> map) { + if (map instanceof ImmutableClassToInstanceMap) { + return (ImmutableClassToInstanceMap) (Map) map; + } + return new Builder().putAll(map).build(); + } + + private final ImmutableMap, B> delegate; + + private ImmutableClassToInstanceMap( + ImmutableMap, B> delegate) { + this.delegate = delegate; + } + + @Override protected Map, B> delegate() { + return delegate; + } + + @SuppressWarnings("unchecked") // value could not get in if not a T + public T getInstance(Class type) { + return (T) delegate.get(type); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public T putInstance(Class type, T value) { + throw new UnsupportedOperationException(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableCollection.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableCollection.java new file mode 100644 index 00000000000..aad71ebdf85 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableCollection.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +import javax.annotation.Nullable; + +/** + * An immutable collection. Does not permit null elements. + * + *

Note: Although this class is not final, it cannot be subclassed + * outside of this package as it has no public or protected constructors. Thus, + * instances of this type are guaranteed to be immutable. + * + * @author Jesse Wilson + */ +@GwtCompatible +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableCollection + implements Collection, Serializable { + static final ImmutableCollection EMPTY_IMMUTABLE_COLLECTION + = new EmptyImmutableCollection(); + + ImmutableCollection() {} + + /** + * Returns an unmodifiable iterator across the elements in this collection. + */ + public abstract UnmodifiableIterator iterator(); + + public Object[] toArray() { + Object[] newArray = new Object[size()]; + return toArray(newArray); + } + + public T[] toArray(T[] other) { + int size = size(); + if (other.length < size) { + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + + // Writes will produce ArrayStoreException when the toArray() doc requires. + Object[] otherAsObjectArray = other; + int index = 0; + for (E element : this) { + otherAsObjectArray[index++] = element; + } + return other; + } + + public boolean contains(@Nullable Object object) { + if (object == null) { + return false; + } + for (E element : this) { + if (element.equals(object)) { + return true; + } + } + return false; + } + + public boolean containsAll(Collection targets) { + for (Object target : targets) { + if (!contains(target)) { + return false; + } + } + return true; + } + + public boolean isEmpty() { + return size() == 0; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(size() * 16).append('['); + Collections2.standardJoiner.appendTo(sb, this); + return sb.append(']').toString(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean add(E e) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean remove(Object object) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean addAll(Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean removeAll(Collection oldElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean retainAll(Collection elementsToKeep) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void clear() { + throw new UnsupportedOperationException(); + } + + private static class EmptyImmutableCollection + extends ImmutableCollection { + public int size() { + return 0; + } + + @Override public boolean isEmpty() { + return true; + } + + @Override public boolean contains(@Nullable Object object) { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.EMPTY_ITERATOR; + } + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + @Override public Object[] toArray() { + return EMPTY_ARRAY; + } + + @Override public T[] toArray(T[] array) { + if (array.length > 0) { + array[0] = null; + } + return array; + } + } + + private static class ArrayImmutableCollection + extends ImmutableCollection { + private final E[] elements; + + ArrayImmutableCollection(E[] elements) { + this.elements = elements; + } + + public int size() { + return elements.length; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.forArray(elements); + } + } + + /* + * Serializes ImmutableCollections as their logical contents. This ensures + * that implementation types do not leak into the serialized representation. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return elements.length == 0 + ? EMPTY_IMMUTABLE_COLLECTION + : new ArrayImmutableCollection(Platform.clone(elements)); + } + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Abstract base class for builders of {@link ImmutableCollection} types. + */ + abstract static class Builder { + /** + * Adds {@code element} to the {@code ImmutableCollection} being built. + * + *

Note that each builder class covariantly returns its own type from + * this method. + * + * @param element the element to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code element} is null + */ + public abstract Builder add(E element); + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} + * being built. + * + *

Note that each builder class overrides this method in order to + * covariantly return its own type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + public Builder add(E... elements) { + checkNotNull(elements); // for GWT + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} + * being built. + * + *

Note that each builder class overrides this method in order to + * covariantly return its own type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + public Builder addAll(Iterable elements) { + checkNotNull(elements); // for GWT + for (E element : elements) { + add(element); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableCollection} + * being built. + * + *

Note that each builder class overrides this method in order to + * covariantly return its own type. + * + * @param elements the elements to add + * @return this {@code Builder} instance + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + public Builder addAll(Iterator elements) { + checkNotNull(elements); // for GWT + while (elements.hasNext()) { + add(elements.next()); + } + return this; + } + + /** + * Returns a newly-created {@code ImmutableCollection} of the appropriate + * type, containing the elements provided to this builder. + * + *

Note that each builder class covariantly returns the appropriate type + * of {@code ImmutableCollection} from this method. + */ + public abstract ImmutableCollection build(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableEntry.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableEntry.java new file mode 100644 index 00000000000..788fa5ca27f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableEntry.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** + * @see org.elasticsearch.util.gcommon.collect.Maps#immutableEntry(Object, Object) + */ +@GwtCompatible +class ImmutableEntry extends AbstractMapEntry + implements Serializable { + private final K key; + private final V value; + + ImmutableEntry(@Nullable K key, @Nullable V value) { + this.key = key; + this.value = value; + } + + @Override public K getKey() { + return key; + } + + @Override public V getValue() { + return value; + } + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableEnumSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableEnumSet.java new file mode 100644 index 00000000000..ef761628a32 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableEnumSet.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Set; + +/** + * Implementation of {@link ImmutableSet} backed by a non-empty {@link + * java.util.EnumSet}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +final class ImmutableEnumSet*/> extends ImmutableSet { + /* + * Notes on EnumSet and >: + * + * This class isn't an arbitrary ForwardingImmutableSet because we need to + * know that calling {@code clone()} during deserialization will return an + * object that no one else has a reference to, allowing us to guarantee + * immutability. Hence, we support only {@link EnumSet}. + * + * GWT complicates matters. If we declare the class's type parameter as + * > (as is necessary to declare a field of type + * EnumSet), GWT generates serializers for every available enum. This + * increases the size of some applications' JavaScript by over 10%. To avoid + * this, we declare the type parameter as just and the field as just + * Set. writeReplace() must then use an unchecked cast to return to + * EnumSet, guaranteeing immutability as described above. + */ + private final transient Set delegate; + + ImmutableEnumSet(Set delegate) { + this.delegate = delegate; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.unmodifiableIterator(delegate.iterator()); + } + + public int size() { + return delegate.size(); + } + + @Override public boolean contains(Object object) { + return delegate.contains(object); + } + + @Override public boolean containsAll(Collection collection) { + return delegate.containsAll(collection); + } + + @Override public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override public Object[] toArray() { + return delegate.toArray(); + } + + @Override public T[] toArray(T[] array) { + return delegate.toArray(array); + } + + @Override public boolean equals(Object object) { + return object == this || delegate.equals(object); + } + + private transient int hashCode; + + @Override public int hashCode() { + int result = hashCode; + return (result == 0) ? hashCode = delegate.hashCode() : result; + } + + @Override public String toString() { + return delegate.toString(); + } + + // All callers of the constructor are restricted to >. + @SuppressWarnings("unchecked") + @Override Object writeReplace() { + return new EnumSerializedForm((EnumSet) delegate); + } + + /* + * This class is used to serialize ImmutableEnumSet instances. + */ + private static class EnumSerializedForm> + implements Serializable { + final EnumSet delegate; + EnumSerializedForm(EnumSet delegate) { + this.delegate = delegate; + } + Object readResolve() { + // EJ2 #76: Write readObject() methods defensively. + return new ImmutableEnumSet(delegate.clone()); + } + private static final long serialVersionUID = 0; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableList.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableList.java new file mode 100644 index 00000000000..cb1a25ba795 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableList.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; + +import javax.annotation.Nullable; + +/** + * A high-performance, immutable, random-access {@code List} implementation. + * Does not permit null elements. + * + *

Unlike {@link Collections#unmodifiableList}, which is a view of a + * separate collection that can still change, an instance of {@code + * ImmutableList} contains its own private data and will never change. + * {@code ImmutableList} is convenient for {@code public static final} lists + * ("constant lists") and also lets you easily make a "defensive copy" of a list + * provided to your class by a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this type are + * guaranteed to be immutable. + * + * @see ImmutableMap + * @see ImmutableSet + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableList extends ImmutableCollection + implements List, RandomAccess { + /** + * Returns the empty immutable list. This set behaves and performs comparably + * to {@link Collections#emptyList}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the list will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableList of() { + return (ImmutableList) EmptyImmutableList.INSTANCE; + } + + /** + * Returns an immutable list containing a single element. This list behaves + * and performs comparably to {@link Collections#singleton}, but will not + * accept a null element. It is preferable mainly for consistency and + * maintainability of your code. + * + * @throws NullPointerException if {@code element} is null + */ + public static ImmutableList of(E element) { + return new SingletonImmutableList(element); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2) { + return new RegularImmutableList(copyIntoArray(e1, e2)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3) { + return new RegularImmutableList(copyIntoArray(e1, e2, e3)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4) { + return new RegularImmutableList(copyIntoArray(e1, e2, e3, e4)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5) { + return new RegularImmutableList(copyIntoArray(e1, e2, e3, e4, e5)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of(E e1, E e2, E e3, E e4, E e5, E e6) { + return new RegularImmutableList(copyIntoArray(e1, e2, e3, e4, e5, e6)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7) { + return new RegularImmutableList( + copyIntoArray(e1, e2, e3, e4, e5, e6, e7)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) { + return new RegularImmutableList( + copyIntoArray(e1, e2, e3, e4, e5, e6, e7, e8)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) { + return new RegularImmutableList( + copyIntoArray(e1, e2, e3, e4, e5, e6, e7, e8, e9)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) { + return new RegularImmutableList( + copyIntoArray(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10)); + } + + /** + * Identical to {@link #of(Object[])}. + * + * @throws NullPointerException if any element is null + */ + public static ImmutableList of( + E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) { + return new RegularImmutableList( + copyIntoArray(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11)); + } + + // These go up to eleven. After that, you just get the varargs form, and + // whatever warnings might come along with it. :( + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableList of(E... elements) { + checkNotNull(elements); // for GWT + switch (elements.length) { + case 0: + return ImmutableList.of(); + case 1: + return new SingletonImmutableList(elements[0]); + default: + return new RegularImmutableList(copyIntoArray(elements)); + } + } + + /** + * Returns an immutable list containing the given elements, in order. This + * method iterates over {@code elements} at most once. Note that if {@code + * list} is a {@code List}, then {@code ImmutableList.copyOf(list)} + * returns an {@code ImmutableList} containing each of the strings + * in {@code list}, while ImmutableList.of(list)} returns an {@code + * ImmutableList>} containing one element (the given list + * itself). + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableList}, no copy will actually be performed, and the + * given list itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableList copyOf(Iterable elements) { + if (elements instanceof ImmutableList) { + /* + * TODO: If the given ImmutableList is a sublist, copy the referenced + * portion of the array into a new array to save space? + */ + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableList list = (ImmutableList) elements; + return list; + } else if (elements instanceof Collection) { + @SuppressWarnings("unchecked") + Collection coll = (Collection) elements; + return copyOfInternal(coll); + } else { + return copyOfInternal(Lists.newArrayList(elements)); + } + } + + /** + * Returns an immutable list containing the given elements, in order. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableList copyOf(Iterator elements) { + return copyOfInternal(Lists.newArrayList(elements)); + } + + private static ImmutableList copyOfInternal( + ArrayList list) { + switch (list.size()) { + case 0: + return of(); + case 1: + return new SingletonImmutableList(list.iterator().next()); + default: + return new RegularImmutableList(nullChecked(list.toArray())); + } + } + + /** + * Checks that all the array elements are non-null. + * + * @return the argument array + * @throws NullPointerException if any element is null + */ + private static Object[] nullChecked(Object[] array) { + for (int i = 0, len = array.length; i < len; i++) { + if (array[i] == null) { + throw new NullPointerException("at index " + i); + } + } + return array; + } + + private static ImmutableList copyOfInternal( + Collection collection) { + int size = collection.size(); + return (size == 0) + ? ImmutableList.of() + : ImmutableList.createFromIterable(collection, size); + } + + ImmutableList() {} + + // This declaration is needed to make List.iterator() and + // ImmutableCollection.iterator() consistent. + @Override public abstract UnmodifiableIterator iterator(); + + // Mark these two methods with @Nullable + + public abstract int indexOf(@Nullable Object object); + + public abstract int lastIndexOf(@Nullable Object object); + + // constrain the return type to ImmutableList + + /** + * Returns an immutable list of the elements between the specified {@code + * fromIndex}, inclusive, and {@code toIndex}, exclusive. (If {@code + * fromIndex} and {@code toIndex} are equal, the empty immutable list is + * returned.) + */ + public abstract ImmutableList subList(int fromIndex, int toIndex); + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final boolean addAll(int index, Collection newElements) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the list unmodified. + * + * @throws UnsupportedOperationException always + */ + public final E remove(int index) { + throw new UnsupportedOperationException(); + } + + private static Object[] copyIntoArray(Object... source) { + Object[] array = new Object[source.length]; + int index = 0; + for (Object element : source) { + if (element == null) { + throw new NullPointerException("at index " + index); + } + array[index++] = element; + } + return array; + } + + private static ImmutableList createFromIterable( + Iterable source, int estimatedSize) { + Object[] array = new Object[estimatedSize]; + int index = 0; + + for (Object element : source) { + if (index == estimatedSize) { + // At least one element was added after our call to size(). + estimatedSize = ((estimatedSize / 2) + 1) * 3; + array = copyOf(array, estimatedSize); + } + if (element == null) { + throw new NullPointerException("at index " + index); + } + array[index++] = element; + } + + if (index == 0) { + return of(); + } else if (index == 1) { + // The elements of "array" come from a Iterable. + @SuppressWarnings("unchecked") + E element = (E) array[0]; + return of(element); + } + + if (index != estimatedSize) { + array = copyOf(array, index); + } + + return new RegularImmutableList(array, 0, index); + } + + // Avoid using Arrays.copyOf(), which is not present until JDK6. + private static Object[] copyOf(Object[] oldArray, int newSize) { + Object[] newArray = new Object[newSize]; + System.arraycopy(oldArray, 0, newArray, 0, + Math.min(oldArray.length, newSize)); + return newArray; + } + + /* + * Serializes ImmutableLists as their logical contents. This ensures that + * implementation types do not leak into the serialized representation. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return of(elements); + } + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable list instances, especially + * {@code public static final} lists ("constant lists"). + * + *

Example: + *

   {@code
+   *   public static final ImmutableList GOOGLE_COLORS
+   *       = new ImmutableList.Builder()
+   *           .addAll(WEBSAFE_COLORS)
+   *           .add(new Color(0, 191, 255))
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple lists in series. Each new list + * contains the one created before it. + */ + public static final class Builder extends ImmutableCollection.Builder { + private final ArrayList contents = Lists.newArrayList(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableList#builder}. + */ + public Builder() {} + + /** + * Adds {@code element} to the {@code ImmutableList}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + contents.add(checkNotNull(element)); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterable elements) { + if (elements instanceof Collection) { + Collection collection = (Collection) elements; + contents.ensureCapacity(contents.size() + collection.size()); + } + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder add(E... elements) { + checkNotNull(elements); // for GWT + contents.ensureCapacity(contents.size() + elements.length); + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableList}. + * + * @param elements the {@code Iterable} to add to the {@code ImmutableList} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableList} based on the contents of + * the {@code Builder}. + */ + @Override public ImmutableList build() { + return copyOf(contents); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableListMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableListMultimap.java new file mode 100644 index 00000000000..acf3c508a6c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableListMultimap.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable {@link ListMultimap} with reliable user-specified key and value + * iteration order. Does not permit null keys or values. + * + *

Unlike {@link Multimaps#unmodifiableListMultimap(ListMultimap)}, which is + * a view of a separate multimap which can still change, an instance of + * {@code ImmutableListMultimap} contains its own data and will never + * change. {@code ImmutableListMultimap} is convenient for + * {@code public static final} multimaps ("constant multimaps") and also lets + * you easily make a "defensive copy" of a multimap provided to your class by + * a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class + * are guaranteed to be immutable. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public class ImmutableListMultimap + extends ImmutableMultimap + implements ListMultimap { + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableListMultimap of() { + return (ImmutableListMultimap) EmptyImmutableListMultimap.INSTANCE; + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableListMultimap of(K k1, V v1) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of(K k1, V v1, K k2, V v2) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableListMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable {@code ListMultimap} instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + *

   {@code
+   *
+   *   static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *       new ImmutableListMultimap.Builder()
+   *           .put("one", 1)
+   *           .putAll("several", 1, 2, 3)
+   *           .putAll("many", 1, 2, 3, 4, 5)
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple multimaps in series. Each multimap + * contains the key-value mappings in the previously created multimaps. + */ + public static final class Builder + extends ImmutableMultimap.Builder { + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableListMultimap#builder}. + */ + public Builder() {} + + /** + * Adds a key-value mapping to the built multimap. + */ + @Override public Builder put(K key, V value) { + super.put(key, value); + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any + * element in {@code values} is null. The builder is left in an invalid + * state. + */ + @Override public Builder putAll(K key, Iterable values) { + super.putAll(key, values); + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. The builder + * is left in an invalid state. + */ + @Override public Builder putAll(K key, V... values) { + super.putAll(key, values); + return this; + } + + /** + * Stores another multimap's entries in the built multimap. The generated + * multimap's key and value orderings correspond to the iteration ordering + * of the {@code multimap.asMap()} view, with new keys and values following + * any existing keys and values. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null. The builder is left in an invalid state. + */ + @Override public Builder putAll( + Multimap multimap) { + super.putAll(multimap); + return this; + } + + /** + * Returns a newly-created immutable multimap. + */ + @Override public ImmutableListMultimap build() { + return (ImmutableListMultimap) super.build(); + } + } + + /** + * Returns an immutable multimap containing the same mappings as + * {@code multimap}. The generated multimap's key and value orderings + * correspond to the iteration ordering of the {@code multimap.asMap()} view. + * + *

Note: Despite what the method name suggests, if + * {@code multimap} is an {@code ImmutableListMultimap}, no copy will actually + * be performed, and the given multimap itself will be returned. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static ImmutableListMultimap copyOf( + Multimap multimap) { + if (multimap.isEmpty()) { + return of(); + } + + if (multimap instanceof ImmutableListMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableListMultimap kvMultimap + = (ImmutableListMultimap) multimap; + return kvMultimap; + } + + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int size = 0; + + for (Map.Entry> entry + : multimap.asMap().entrySet()) { + ImmutableList list = ImmutableList.copyOf(entry.getValue()); + if (!list.isEmpty()) { + builder.put(entry.getKey(), list); + size += list.size(); + } + } + + return new ImmutableListMultimap(builder.build(), size); + } + + ImmutableListMultimap(ImmutableMap> map, int size) { + super(map, size); + } + + // views + + /** + * Returns an immutable list of the values for the given key. If no mappings + * in the multimap have the provided key, an empty immutable list is + * returned. The values are in the same order as the parameters used to build + * this multimap. + */ + @Override public ImmutableList get(@Nullable K key) { + // This cast is safe as its type is known in constructor. + ImmutableList list = (ImmutableList) map.get(key); + return (list == null) ? ImmutableList.of() : list; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableList removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableList replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the + * key, the number of values for that key, and the key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder + = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + Object[] array = new Object[valueCount]; + for (int j = 0; j < valueCount; j++) { + array[j] = stream.readObject(); + } + builder.put(key, ImmutableList.of(array)); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.build(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) + new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMap.java new file mode 100644 index 00000000000..828e6f97bcc --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMap.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import static org.elasticsearch.util.gcommon.collect.Iterables.getOnlyElement; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable, hash-based {@link Map} with reliable user-specified iteration + * order. Does not permit null keys or values. + * + *

Unlike {@link Collections#unmodifiableMap}, which is a view of a + * separate map which can still change, an instance of {@code ImmutableMap} + * contains its own data and will never change. {@code ImmutableMap} is + * convenient for {@code public static final} maps ("constant maps") and also + * lets you easily make a "defensive copy" of a map provided to your class by a + * caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @see ImmutableList + * @see ImmutableSet + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableMap implements Map, Serializable { + /** + * Returns the empty map. This map behaves and performs comparably to + * {@link Collections#emptyMap}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableMap of() { + return (ImmutableMap) EmptyImmutableMap.INSTANCE; + } + + /** + * Returns an immutable map containing a single entry. This map behaves and + * performs comparably to {@link Collections#singletonMap} but will not accept + * a null key or value. It is preferable mainly for consistency and + * maintainability of your code. + */ + public static ImmutableMap of(K k1, V v1) { + return new SingletonImmutableMap( + checkNotNull(k1), checkNotNull(v1)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of(K k1, V v1, K k2, V v2) { + return new RegularImmutableMap(entryOf(k1, v1), entryOf(k2, v2)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return new RegularImmutableMap( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new RegularImmutableMap( + entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4)); + } + + /** + * Returns an immutable map containing the given entries, in order. + * + * @throws IllegalArgumentException if duplicate keys are provided + */ + public static ImmutableMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new RegularImmutableMap(entryOf(k1, v1), + entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5)); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Verifies that {@code key} and {@code value} are non-null, and returns a new + * immutable entry with those values. + * + *

A call to {@link Map.Entry#setValue} on the returned entry will always + * throw {@link UnsupportedOperationException}. + */ + static Entry entryOf(K key, V value) { + return Maps.immutableEntry(checkNotNull(key), checkNotNull(value)); + } + + /** + * A builder for creating immutable map instances, especially {@code public + * static final} maps ("constant maps"). Example:

   {@code
+   *
+   *   static final ImmutableMap WORD_TO_INT =
+   *       new ImmutableMap.Builder()
+   *           .put("one", 1)
+   *           .put("two", 2)
+   *           .put("three", 3)
+   *           .build();}
+ * + * For small immutable maps, the {@code ImmutableMap.of()} methods are + * even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple maps in series. Each map is a superset of + * the maps created before it. + */ + public static class Builder { + final List> entries = Lists.newArrayList(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMap#builder}. + */ + public Builder() {} + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys are not allowed, and will cause {@link #build} to fail. + */ + public Builder put(K key, V value) { + entries.add(entryOf(key, value)); + return this; + } + + /** + * Associates all of the given map's keys and values in the built map. + * Duplicate keys are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public Builder putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + // TODO: Should build() and the ImmutableBiMap & ImmutableSortedMap versions + // throw an IllegalStateException instead? + + /** + * Returns a newly-created immutable map. + * + * @throws IllegalArgumentException if duplicate keys were added + */ + public ImmutableMap build() { + return fromEntryList(entries); + } + + private static ImmutableMap fromEntryList( + List> entries) { + int size = entries.size(); + switch (size) { + case 0: + return of(); + case 1: + return new SingletonImmutableMap(getOnlyElement(entries)); + default: + Entry[] entryArray + = entries.toArray(new Entry[entries.size()]); + return new RegularImmutableMap(entryArray); + } + } + } + + /** + * Returns an immutable map containing the same entries as {@code map}. If + * {@code map} somehow contains entries with duplicate keys (for example, if + * it is a {@code SortedMap} whose comparator is not consistent with + * equals), the results of this method are undefined. + * + *

Note: Despite what the method name suggests, if {@code map} is an + * {@code ImmutableMap}, no copy will actually be performed, and the given map + * itself will be returned. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableMap copyOf( + Map map) { + if ((map instanceof ImmutableMap) && !(map instanceof ImmutableSortedMap)) { + @SuppressWarnings("unchecked") // safe since map is not writable + ImmutableMap kvMap = (ImmutableMap) map; + return kvMap; + } + + @SuppressWarnings("unchecked") // we won't write to this array + Entry[] entries = map.entrySet().toArray(new Entry[0]); + switch (entries.length) { + case 0: + return of(); + case 1: + return new SingletonImmutableMap(entryOf( + entries[0].getKey(), entries[0].getValue())); + default: + for (int i = 0; i < entries.length; i++) { + K k = entries[i].getKey(); + V v = entries[i].getValue(); + entries[i] = entryOf(k, v); + } + return new RegularImmutableMap(entries); + } + } + + ImmutableMap() {} + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final V put(K k, V v) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final V remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void putAll(Map map) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the map unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void clear() { + throw new UnsupportedOperationException(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public boolean containsKey(@Nullable Object key) { + return get(key) != null; + } + + // Overriding to mark it Nullable + public abstract boolean containsValue(@Nullable Object value); + + // Overriding to mark it Nullable + public abstract V get(@Nullable Object key); + + /** + * Returns an immutable set of the mappings in this map. The entries are in + * the same order as the parameters used to build this map. + */ + public abstract ImmutableSet> entrySet(); + + /** + * Returns an immutable set of the keys in this map. These keys are in + * the same order as the parameters used to build this map. + */ + public abstract ImmutableSet keySet(); + + /** + * Returns an immutable collection of the values in this map. The values are + * in the same order as the parameters used to build this map. + */ + public abstract ImmutableCollection values(); + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Map) { + Map that = (Map) object; + return this.entrySet().equals(that.entrySet()); + } + return false; + } + + @Override public int hashCode() { + // not caching hash code since it could change if map values are mutable + // in a way that modifies their hash codes + return entrySet().hashCode(); + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(size() * 16).append('{'); + Maps.standardJoiner.appendTo(result, this); + return result.append('}').toString(); + } + + /** + * Serialized type for all ImmutableMap instances. It captures the logical + * contents and they are reconstructed using public factory methods. This + * ensures that the implementation types remain as implementation details. + */ + static class SerializedForm implements Serializable { + private final Object[] keys; + private final Object[] values; + SerializedForm(ImmutableMap map) { + keys = new Object[map.size()]; + values = new Object[map.size()]; + int i = 0; + for (Entry entry : map.entrySet()) { + keys[i] = entry.getKey(); + values[i] = entry.getValue(); + i++; + } + } + Object readResolve() { + Builder builder = new Builder(); + return createMap(builder); + } + Object createMap(Builder builder) { + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i], values[i]); + } + return builder.build(); + } + private static final long serialVersionUID = 0; + } + + Object writeReplace() { + return new SerializedForm(this); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMultimap.java new file mode 100644 index 00000000000..085ebbafb3d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMultimap.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable {@link Multimap}. Does not permit null keys or values. + * + *

Unlike {@link Multimaps#unmodifiableMultimap(Multimap)}, which is + * a view of a separate multimap which can still change, an instance of + * {@code ImmutableMultimap} contains its own data and will never + * change. {@code ImmutableMultimap} is convenient for + * {@code public static final} multimaps ("constant multimaps") and also lets + * you easily make a "defensive copy" of a multimap provided to your class by + * a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class + * are guaranteed to be immutable. + * + * @author Jared Levy + */ +@GwtCompatible +public abstract class ImmutableMultimap + implements Multimap, Serializable { + + /** Returns an empty multimap. */ + public static ImmutableMultimap of() { + return ImmutableListMultimap.of(); + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableMultimap of(K k1, V v1) { + return ImmutableListMultimap.of(k1, v1); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of(K k1, V v1, K k2, V v2) { + return ImmutableListMultimap.of(k1, v1, k2, v2); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + */ + public static ImmutableMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return ImmutableListMultimap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Multimap for {@link ImmutableMultimap.Builder} that maintains key and + * value orderings, allows duplicate values, and performs better than + * {@link LinkedListMultimap}. + */ + private static class BuilderMultimap extends AbstractMultimap { + BuilderMultimap() { + super(new LinkedHashMap>()); + } + @Override Collection createCollection() { + return Lists.newArrayList(); + } + private static final long serialVersionUID = 0; + } + + /** + * A builder for creating immutable multimap instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + *

   {@code
+   *
+   *   static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *       new ImmutableMultimap.Builder()
+   *           .put("one", 1)
+   *           .putAll("several", 1, 2, 3)
+   *           .putAll("many", 1, 2, 3, 4, 5)
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple multimaps in series. Each multimap + * contains the key-value mappings in the previously created multimaps. + */ + public static class Builder { + private final Multimap builderMultimap = new BuilderMultimap(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMultimap#builder}. + */ + public Builder() {} + + /** + * Adds a key-value mapping to the built multimap. + */ + public Builder put(K key, V value) { + builderMultimap.put(checkNotNull(key), checkNotNull(value)); + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any + * element in {@code values} is null. The builder is left in an invalid + * state. + */ + public Builder putAll(K key, Iterable values) { + Collection valueList = builderMultimap.get(checkNotNull(key)); + for (V value : values) { + valueList.add(checkNotNull(value)); + } + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. The builder + * is left in an invalid state. + */ + public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + /** + * Stores another multimap's entries in the built multimap. The generated + * multimap's key and value orderings correspond to the iteration ordering + * of the {@code multimap.asMap()} view, with new keys and values following + * any existing keys and values. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null. The builder is left in an invalid state. + */ + public Builder putAll(Multimap multimap) { + for (Map.Entry> entry + : multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Returns a newly-created immutable multimap. + */ + public ImmutableMultimap build() { + return copyOf(builderMultimap); + } + } + + /** + * Returns an immutable multimap containing the same mappings as + * {@code multimap}. The generated multimap's key and value orderings + * correspond to the iteration ordering of the {@code multimap.asMap()} view. + * + *

Note: Despite what the method name suggests, if + * {@code multimap} is an {@code ImmutableMultimap}, no copy will actually be + * performed, and the given multimap itself will be returned. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static ImmutableMultimap copyOf( + Multimap multimap) { + if (multimap instanceof ImmutableMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableMultimap kvMultimap + = (ImmutableMultimap) multimap; + return kvMultimap; + } else { + return ImmutableListMultimap.copyOf(multimap); + } + } + + final transient ImmutableMap> map; + final transient int size; + + // These constants allow the deserialization code to set final fields. This + // holder class makes sure they are not initialized unless an instance is + // deserialized. + static class FieldSettersHolder { + // Eclipse doesn't like the raw ImmutableMultimap + @SuppressWarnings("unchecked") + static final Serialization.FieldSetter + MAP_FIELD_SETTER = Serialization.getFieldSetter( + ImmutableMultimap.class, "map"); + // Eclipse doesn't like the raw ImmutableMultimap + @SuppressWarnings("unchecked") + static final Serialization.FieldSetter + SIZE_FIELD_SETTER = Serialization.getFieldSetter( + ImmutableMultimap.class, "size"); + } + + ImmutableMultimap(ImmutableMap> map, + int size) { + this.map = map; + this.size = size; + } + + // mutators (not supported) + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public ImmutableCollection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public ImmutableCollection replaceValues(K key, + Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an immutable collection of the values for the given key. If no + * mappings in the multimap have the provided key, an empty immutable + * collection is returned. The values are in the same order as the parameters + * used to build this multimap. + */ + public abstract ImmutableCollection get(K key); + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + // accessors + + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + Collection values = map.get(key); + return values != null && values.contains(value); + } + + public boolean containsKey(@Nullable Object key) { + return map.containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + for (Collection valueCollection : map.values()) { + if (valueCollection.contains(value)) { + return true; + } + } + return false; + } + + public boolean isEmpty() { + return size == 0; + } + + public int size() { + return size; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return this.map.equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return map.hashCode(); + } + + @Override public String toString() { + return map.toString(); + } + + // views + + /** + * Returns an immutable set of the distinct keys in this multimap. These keys + * are ordered according to when they first appeared during the construction + * of this multimap. + */ + public ImmutableSet keySet() { + return map.keySet(); + } + + /** + * Returns an immutable map that associates each key with its corresponding + * values in the multimap. + */ + @SuppressWarnings("unchecked") // a widening cast + public ImmutableMap> asMap() { + return (ImmutableMap) map; + } + + private transient ImmutableCollection> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. Its + * iterator traverses the values for the first key, the values for the second + * key, and so on. + */ + public ImmutableCollection> entries() { + ImmutableCollection> result = entries; + return (result == null) + ? (entries = new EntryCollection(this)) : result; + } + + private static class EntryCollection + extends ImmutableCollection> { + final ImmutableMultimap multimap; + + EntryCollection(ImmutableMultimap multimap) { + this.multimap = multimap; + } + + @Override public UnmodifiableIterator> iterator() { + final Iterator>> + mapIterator = this.multimap.map.entrySet().iterator(); + + return new UnmodifiableIterator>() { + K key; + Iterator valueIterator; + + public boolean hasNext() { + return (key != null && valueIterator.hasNext()) + || mapIterator.hasNext(); + } + + public Map.Entry next() { + if (key == null || !valueIterator.hasNext()) { + Map.Entry> entry + = mapIterator.next(); + key = entry.getKey(); + valueIterator = entry.getValue().iterator(); + } + return Maps.immutableEntry(key, valueIterator.next()); + } + }; + } + + public int size() { + return multimap.size(); + } + + @Override public boolean contains(Object object) { + if (object instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) object; + return multimap.containsEntry(entry.getKey(), entry.getValue()); + } + return false; + } + + private static final long serialVersionUID = 0; + } + + private transient ImmutableMultiset keys; + + /** + * Returns a collection, which may contain duplicates, of all keys. The number + * of times a key appears in the returned multiset equals the number of + * mappings the key has in the multimap. Duplicate keys appear consecutively + * in the multiset's iteration order. + */ + public ImmutableMultiset keys() { + ImmutableMultiset result = keys; + return (result == null) ? (keys = createKeys()) : result; + } + + private ImmutableMultiset createKeys() { + ImmutableMultiset.Builder builder = ImmutableMultiset.builder(); + for (Map.Entry> entry + : map.entrySet()) { + builder.addCopies(entry.getKey(), entry.getValue().size()); + } + return builder.build(); + } + + private transient ImmutableCollection values; + + /** + * Returns an immutable collection of the values in this multimap. Its + * iterator traverses the values for the first key, the values for the second + * key, and so on. + */ + public ImmutableCollection values() { + ImmutableCollection result = values; + return (result == null) ? (values = new Values(this)) : result; + } + + private static class Values extends ImmutableCollection { + final Multimap multimap; + + Values(Multimap multimap) { + this.multimap = multimap; + } + + @Override public UnmodifiableIterator iterator() { + final Iterator> entryIterator + = multimap.entries().iterator(); + return new UnmodifiableIterator() { + public boolean hasNext() { + return entryIterator.hasNext(); + } + public V next() { + return entryIterator.next().getValue(); + } + }; + } + + public int size() { + return multimap.size(); + } + + private static final long serialVersionUID = 0; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMultiset.java new file mode 100644 index 00000000000..63c949db9f0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableMultiset.java @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import org.elasticsearch.util.gcommon.collect.Serialization.FieldSetter; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An immutable hash-based multiset. Does not permit null elements. + * + *

Its iterator orders elements according to the first appearance of the + * element among the items passed to the factory method or builder. When the + * multiset contains multiple instances of an element, those instances are + * consecutive in the iteration order. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public class ImmutableMultiset extends ImmutableCollection + implements Multiset { + + /** + * Returns the empty immutable multiset. + */ + @SuppressWarnings("unchecked") // all supported methods are covariant + public static ImmutableMultiset of() { + return (ImmutableMultiset) EmptyImmutableMultiset.INSTANCE; + } + + /** + * Returns an immutable multiset containing the given elements. + * + *

The multiset is ordered by the first occurrence of each element. For + * example, {@code ImmutableMultiset.of(2, 3, 1, 3)} yields a multiset with + * elements in the order {@code 2, 3, 3, 1}. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset of(E... elements) { + return copyOf(Arrays.asList(elements)); + } + + /** + * Returns an immutable multiset containing the given elements. + * + *

The multiset is ordered by the first occurrence of each element. For + * example, {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3))} yields + * a multiset with elements in the order {@code 2, 3, 3, 1}. + * + *

Note that if {@code c} is a {@code Collection}, then {@code + * ImmutableMultiset.copyOf(c)} returns an {@code ImmutableMultiset} + * containing each of the strings in {@code c}, while + * {@code ImmutableMultiset.of(c)} returns an + * {@code ImmutableMultiset>} containing one element (the + * given collection itself). + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableMultiset}, no copy will actually be performed, and + * the given multiset itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf( + Iterable elements) { + if (elements instanceof ImmutableMultiset) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableMultiset result = (ImmutableMultiset) elements; + return result; + } + + @SuppressWarnings("unchecked") // the cast causes a warning + Multiset multiset = (elements instanceof Multiset) + ? (Multiset) elements + : LinkedHashMultiset.create(elements); + + return copyOfInternal(multiset); + } + + private static ImmutableMultiset copyOfInternal( + Multiset multiset) { + long size = 0; + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (Entry entry : multiset.entrySet()) { + int count = entry.getCount(); + if (count > 0) { + // Since ImmutableMap.Builder throws an NPE if an element is null, no + // other null checks are needed. + builder.put(entry.getElement(), count); + size += count; + } + } + + if (size == 0) { + return of(); + } + return new ImmutableMultiset( + builder.build(), (int) Math.min(size, Integer.MAX_VALUE)); + } + + /** + * Returns an immutable multiset containing the given elements. + * + *

The multiset is ordered by the first occurrence of each element. For + * example, + * {@code ImmutableMultiset.copyOf(Arrays.asList(2, 3, 1, 3).iterator())} + * yields a multiset with elements in the order {@code 2, 3, 3, 1}. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableMultiset copyOf( + Iterator elements) { + Multiset multiset = LinkedHashMultiset.create(); + Iterators.addAll(multiset, elements); + return copyOfInternal(multiset); + } + + private final transient ImmutableMap map; + private final transient int size; + + // These constants allow the deserialization code to set final fields. This + // holder class makes sure they are not initialized unless an instance is + // deserialized. + @SuppressWarnings("unchecked") + // eclipse doesn't like the raw types here, but they're harmless + private static class FieldSettersHolder { + static final FieldSetter MAP_FIELD_SETTER + = Serialization.getFieldSetter(ImmutableMultiset.class, "map"); + static final FieldSetter SIZE_FIELD_SETTER + = Serialization.getFieldSetter(ImmutableMultiset.class, "size"); + } + + ImmutableMultiset(ImmutableMap map, int size) { + this.map = map; + this.size = size; + } + + public int count(@Nullable Object element) { + Integer value = map.get(element); + return (value == null) ? 0 : value; + } + + @Override public UnmodifiableIterator iterator() { + final Iterator> mapIterator + = map.entrySet().iterator(); + + return new UnmodifiableIterator() { + int remaining; + E element; + + public boolean hasNext() { + return (remaining > 0) || mapIterator.hasNext(); + } + + public E next() { + if (remaining <= 0) { + Map.Entry entry = mapIterator.next(); + element = entry.getKey(); + remaining = entry.getValue(); + } + remaining--; + return element; + } + }; + } + + public int size() { + return size; + } + + @Override public boolean contains(@Nullable Object element) { + return map.containsKey(element); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the collection unmodified. + * + * @throws UnsupportedOperationException always + */ + public boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + if (this.size() != that.size()) { + return false; + } + for (Entry entry : that.entrySet()) { + if (count(entry.getElement()) != entry.getCount()) { + return false; + } + } + return true; + } + return false; + } + + @Override public int hashCode() { + // could cache this, but not considered worthwhile to do so + return map.hashCode(); + } + + @Override public String toString() { + return entrySet().toString(); + } + + // TODO: Serialization of the element set should serialize the multiset, and + // deserialization should call multiset.elementSet(). Then + // reserialized(multiset).elementSet() == reserialized(multiset.elementSet()) + // Currently, those object references differ. + public Set elementSet() { + return map.keySet(); + } + + private transient ImmutableSet> entrySet; + + public Set> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = new EntrySet(this)) : es; + } + + private static class EntrySet extends ImmutableSet> { + final ImmutableMultiset multiset; + + public EntrySet(ImmutableMultiset multiset) { + this.multiset = multiset; + } + + @Override public UnmodifiableIterator> iterator() { + final Iterator> mapIterator + = multiset.map.entrySet().iterator(); + return new UnmodifiableIterator>() { + public boolean hasNext() { + return mapIterator.hasNext(); + } + public Entry next() { + Map.Entry mapEntry = mapIterator.next(); + return + Multisets.immutableEntry(mapEntry.getKey(), mapEntry.getValue()); + } + }; + } + + public int size() { + return multiset.map.size(); + } + + @Override public boolean contains(Object o) { + if (o instanceof Entry) { + Entry entry = (Entry) o; + if (entry.getCount() <= 0) { + return false; + } + int count = multiset.count(entry.getElement()); + return count == entry.getCount(); + } + return false; + } + + @Override public int hashCode() { + return multiset.map.hashCode(); + } + + @Override Object writeReplace() { + return this; + } + + private static final long serialVersionUID = 0; + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int entryCount = stream.readInt(); + ImmutableMap.Builder builder = ImmutableMap.builder(); + long tmpSize = 0; + for (int i = 0; i < entryCount; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultiset + E element = (E) stream.readObject(); + int count = stream.readInt(); + if (count <= 0) { + throw new InvalidObjectException("Invalid count " + count); + } + builder.put(element, count); + tmpSize += count; + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, builder.build()); + FieldSettersHolder.SIZE_FIELD_SETTER.set( + this, (int) Math.min(tmpSize, Integer.MAX_VALUE)); + } + + @Override Object writeReplace() { + return this; + } + + private static final long serialVersionUID = 0; + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable multiset instances, especially + * {@code public static final} multisets ("constant multisets"). + * + *

Example: + *

   {@code
+   *   public static final ImmutableMultiset BEANS
+   *       = new ImmutableMultiset.Builder()
+   *           .addCopies(Bean.COCOA, 4)
+   *           .addCopies(Bean.GARDEN, 6)
+   *           .addCopies(Bean.RED, 8)
+   *           .addCopies(Bean.BLACK_EYED, 10)
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple multisets in series. Each multiset + * is a superset of the multiset created before it. + */ + public static final class Builder extends ImmutableCollection.Builder { + private final Multiset contents = LinkedHashMultiset.create(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableMultiset#builder}. + */ + public Builder() {} + + /** + * Adds {@code element} to the {@code ImmutableMultiset}. + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + contents.add(checkNotNull(element)); + return this; + } + + /** + * Adds a number of occurrences of an element to this {@code + * ImmutableMultiset}. + * + * @param element the element to add + * @param occurrences the number of occurrences of the element to add. May + * be zero, in which case no change will be made. + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code occurrences} is negative, or + * if this operation would result in more than {@link Integer#MAX_VALUE} + * occurrences of the element + */ + public Builder addCopies(E element, int occurrences) { + contents.add(checkNotNull(element), occurrences); + return this; + } + + /** + * Adds or removes the necessary occurrences of an element such that the + * element attains the desired count. + * + * @param element the element to add or remove occurrences of + * @param count the desired count of the element in this multiset + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + * @throws IllegalArgumentException if {@code count} is negative + */ + public Builder setCount(E element, int count) { + contents.setCount(checkNotNull(element), count); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the {@code Iterable} to add to the {@code + * ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterable elements) { + if (elements instanceof Multiset) { + @SuppressWarnings("unchecked") + Multiset multiset = (Multiset) elements; + for (Entry entry : multiset.entrySet()) { + addCopies(entry.getElement(), entry.getCount()); + } + } else { + super.addAll(elements); + } + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableMultiset}. + * + * @param elements the elements to add to the {@code ImmutableMultiset} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableMultiset} based on the contents + * of the {@code Builder}. + */ + @Override public ImmutableMultiset build() { + return copyOf(contents); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSet.java new file mode 100644 index 00000000000..99d64206de8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSet.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A high-performance, immutable {@code Set} with reliable, user-specified + * iteration order. Does not permit null elements. + * + *

Unlike {@link Collections#unmodifiableSet}, which is a view of a + * separate collection that can still change, an instance of this class contains + * its own private data and will never change. This class is convenient + * for {@code public static final} sets ("constant sets") and also lets you + * easily make a "defensive copy" of a set provided to your class by a caller. + * + *

Warning: Like most sets, an {@code ImmutableSet} will not function + * correctly if an element is modified after being placed in the set. For this + * reason, and to avoid general confusion, it is strongly recommended to place + * only immutable objects into this collection. + * + *

This class has been observed to perform significantly better than {@link + * HashSet} for objects with very fast {@link Object#hashCode} implementations + * (as a well-behaved immutable object should). While this class's factory + * methods create hash-based instances, the {@link ImmutableSortedSet} subclass + * performs binary searches instead. + * + *

Note: Although this class is not final, it cannot be subclassed + * outside its package as it has no public or protected constructors. Thus, + * instances of this type are guaranteed to be immutable. + * + * @see ImmutableList + * @see ImmutableMap + * @author Kevin Bourrillion + * @author Nick Kralevich + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSet extends ImmutableCollection + implements Set { + /** + * Returns the empty immutable set. This set behaves and performs comparably + * to {@link Collections#emptySet}, and is preferable mainly for consistency + * and maintainability of your code. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings({"unchecked"}) + public static ImmutableSet of() { + return (ImmutableSet) EmptyImmutableSet.INSTANCE; + } + + /** + * Returns an immutable set containing a single element. This set behaves and + * performs comparably to {@link Collections#singleton}, but will not accept + * a null element. It is preferable mainly for consistency and + * maintainability of your code. + */ + public static ImmutableSet of(E element) { + return new SingletonImmutableSet(element); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSet of(E e1, E e2) { + return create(e1, e2); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSet of(E e1, E e2, E e3) { + return create(e1, e2, e3); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSet of(E e1, E e2, E e3, E e4) { + return create(e1, e2, e3, e4); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSet of(E e1, E e2, E e3, E e4, E e5) { + return create(e1, e2, e3, e4, e5); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored (but too many of these may result in the set being + * sized inappropriately). + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet of(E... elements) { + checkNotNull(elements); // for GWT + switch (elements.length) { + case 0: + return of(); + case 1: + return of(elements[0]); + default: + return create(elements); + } + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored (but too many of these may result in the set being + * sized inappropriately). This method iterates over {@code elements} at most + * once. + * + *

Note that if {@code s} is a {@code Set}, then {@code + * ImmutableSet.copyOf(s)} returns an {@code ImmutableSet} containing + * each of the strings in {@code s}, while {@code ImmutableSet.of(s)} returns + * a {@code ImmutableSet>} containing one element (the given set + * itself). + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableSet} (but not an {@code ImmutableSortedSet}), no copy + * will actually be performed, and the given set itself will be returned. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterable elements) { + if (elements instanceof ImmutableSet + && !(elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") // all supported methods are covariant + ImmutableSet set = (ImmutableSet) elements; + return set; + } + return copyOfInternal(Collections2.toCollection(elements)); + } + + /** + * Returns an immutable set containing the given elements, in order. Repeated + * occurrences of an element (according to {@link Object#equals}) after the + * first are ignored. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSet copyOf(Iterator elements) { + Collection list = Lists.newArrayList(elements); + return copyOfInternal(list); + } + + private static ImmutableSet copyOfInternal( + Collection collection) { + // TODO: Support concurrent collections that change while this method is + // running. + switch (collection.size()) { + case 0: + return of(); + case 1: + // TODO: Remove "ImmutableSet." when eclipse bug is fixed. + return ImmutableSet.of(collection.iterator().next()); + default: + return create(collection, collection.size()); + } + } + + ImmutableSet() {} + + /** Returns {@code true} if the {@code hashCode()} method runs quickly. */ + boolean isHashCodeFast() { + return false; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ImmutableSet + && isHashCodeFast() + && ((ImmutableSet) object).isHashCodeFast() + && hashCode() != object.hashCode()) { + return false; + } + return Collections2.setEquals(this, object); + } + + @Override public int hashCode() { + int hashCode = 0; + for (Object o : this) { + hashCode += o.hashCode(); + } + return hashCode; + } + + // This declaration is needed to make Set.iterator() and + // ImmutableCollection.iterator() consistent. + @Override public abstract UnmodifiableIterator iterator(); + + private static ImmutableSet create(E... elements) { + return create(Arrays.asList(elements), elements.length); + } + + private static ImmutableSet create( + Iterable iterable, int count) { + // count is always the (nonzero) number of elements in the iterable + int tableSize = Hashing.chooseTableSize(count); + Object[] table = new Object[tableSize]; + int mask = tableSize - 1; + + List elements = new ArrayList(count); + int hashCode = 0; + + for (E element : iterable) { + checkNotNull(element); // for GWT + int hash = element.hashCode(); + for (int i = Hashing.smear(hash); true; i++) { + int index = i & mask; + Object value = table[index]; + if (value == null) { + // Came to an empty bucket. Put the element here. + table[index] = element; + elements.add(element); + hashCode += hash; + break; + } else if (value.equals(element)) { + break; // Found a duplicate. Nothing to do. + } + } + } + + if (elements.size() == 1) { + // The iterable contained only duplicates of the same element. + return new SingletonImmutableSet(elements.get(0), hashCode); + } else if (tableSize > Hashing.chooseTableSize(elements.size())) { + // Resize the table when the iterable includes too many duplicates. + return create(elements, elements.size()); + } else { + return new RegularImmutableSet( + elements.toArray(), hashCode, table, mask); + } + } + + abstract static class ArrayImmutableSet extends ImmutableSet { + // the elements (two or more) in the desired order. + final transient Object[] elements; + + ArrayImmutableSet(Object[] elements) { + this.elements = elements; + } + + public int size() { + return elements.length; + } + + @Override public boolean isEmpty() { + return false; + } + + /* + * The cast is safe because the only way to create an instance is via the + * create() method above, which only permits elements of type E. + */ + @SuppressWarnings("unchecked") + @Override public UnmodifiableIterator iterator() { + return (UnmodifiableIterator) Iterators.forArray(elements); + } + + @Override public Object[] toArray() { + Object[] array = new Object[size()]; + System.arraycopy(elements, 0, array, 0, size()); + return array; + } + + @Override public T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = ObjectArrays.newArray(array, size); + } else if (array.length > size) { + array[size] = null; + } + System.arraycopy(elements, 0, array, 0, size); + return array; + } + + @Override public boolean containsAll(Collection targets) { + if (targets == this) { + return true; + } + if (!(targets instanceof ArrayImmutableSet)) { + return super.containsAll(targets); + } + if (targets.size() > size()) { + return false; + } + for (Object target : ((ArrayImmutableSet) targets).elements) { + if (!contains(target)) { + return false; + } + } + return true; + } + } + + /** such as ImmutableMap.keySet() */ + abstract static class TransformedImmutableSet extends ImmutableSet { + final D[] source; + final int hashCode; + + TransformedImmutableSet(D[] source, int hashCode) { + this.source = source; + this.hashCode = hashCode; + } + + abstract E transform(D element); + + public int size() { + return source.length; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public UnmodifiableIterator iterator() { + return new AbstractIterator() { + int index = 0; + @Override protected E computeNext() { + return index < source.length + ? transform(source[index++]) + : endOfData(); + } + }; + } + + @Override public Object[] toArray() { + return toArray(new Object[size()]); + } + + @Override public T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = ObjectArrays.newArray(array, size); + } else if (array.length > size) { + array[size] = null; + } + + // Writes will produce ArrayStoreException when the toArray() doc requires. + Object[] objectArray = array; + for (int i = 0; i < source.length; i++) { + objectArray[i] = transform(source[i]); + } + return array; + } + + @Override public final int hashCode() { + return hashCode; + } + + @Override boolean isHashCodeFast() { + return true; + } + } + + /* + * This class is used to serialize all ImmutableSet instances, except for + * ImmutableEnumSet/ImmutableSortedSet, regardless of implementation type. It + * captures their "logical contents" and they are reconstructed using public + * static factories. This is necessary to ensure that the existence of a + * particular implementation type is an implementation detail. + */ + private static class SerializedForm implements Serializable { + final Object[] elements; + SerializedForm(Object[] elements) { + this.elements = elements; + } + Object readResolve() { + return of(elements); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(toArray()); + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * A builder for creating immutable set instances, especially + * {@code public static final} sets ("constant sets"). + * + *

Example: + *

{@code
+   *   public static final ImmutableSet GOOGLE_COLORS
+   *       = new ImmutableSet.Builder()
+   *           .addAll(WEBSAFE_COLORS)
+   *           .add(new Color(0, 191, 255))
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple sets in series. Each set + * is a superset of the set created before it. + */ + public static class Builder extends ImmutableCollection.Builder { + // accessed directly by ImmutableSortedSet + final ArrayList contents = Lists.newArrayList(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSet#builder}. + */ + public Builder() {} + + /** + * Adds {@code element} to the {@code ImmutableSet}. If the {@code + * ImmutableSet} already contains {@code element}, then {@code add} has no + * effect (only the previously added element is retained). + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + contents.add(checkNotNull(element)); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder add(E... elements) { + checkNotNull(elements); // for GWT + contents.ensureCapacity(contents.size() + elements.length); + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the {@code Iterable} to add to the {@code ImmutableSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterable elements) { + if (elements instanceof Collection) { + Collection collection = (Collection) elements; + contents.ensureCapacity(contents.size() + collection.size()); + } + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} is null or contains a + * null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableSet} based on the contents of + * the {@code Builder}. + */ + @Override public ImmutableSet build() { + return copyOf(contents); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSetMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSetMultimap.java new file mode 100644 index 00000000000..9c7b13b52ce --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSetMultimap.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An immutable {@link SetMultimap} with reliable user-specified key and value + * iteration order. Does not permit null keys or values. + * + *

Unlike {@link Multimaps#unmodifiableSetMultimap(SetMultimap)}, which is + * a view of a separate multimap which can still change, an instance of + * {@code ImmutableSetMultimap} contains its own data and will never + * change. {@code ImmutableSetMultimap} is convenient for + * {@code public static final} multimaps ("constant multimaps") and also lets + * you easily make a "defensive copy" of a multimap provided to your class by + * a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class + * are guaranteed to be immutable. + * + * @author Mike Ward + */ +@GwtCompatible(serializable = true) +public class ImmutableSetMultimap + extends ImmutableMultimap + implements SetMultimap { + + /** Returns the empty multimap. */ + // Casting is safe because the multimap will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableSetMultimap of() { + return (ImmutableSetMultimap) EmptyImmutableSetMultimap.INSTANCE; + } + + /** + * Returns an immutable multimap containing a single entry. + */ + public static ImmutableSetMultimap of(K k1, V v1) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of(K k1, V v1, K k2, V v2) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + return builder.build(); + } + + /** + * Returns an immutable multimap containing the given entries, in order. + * Repeated occurrences of an entry (according to {@link Object#equals}) after + * the first are ignored. + */ + public static ImmutableSetMultimap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder(); + builder.put(k1, v1); + builder.put(k2, v2); + builder.put(k3, v3); + builder.put(k4, v4); + builder.put(k5, v5); + return builder.build(); + } + + // looking for of() with > 5 entries? Use the builder instead. + + /** + * Returns a new {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Multimap for {@link ImmutableSetMultimap.Builder} that maintains key + * and value orderings and performs better than {@link LinkedHashMultimap}. + */ + private static class BuilderMultimap extends AbstractMultimap { + BuilderMultimap() { + super(new LinkedHashMap>()); + } + @Override Collection createCollection() { + return Sets.newLinkedHashSet(); + } + private static final long serialVersionUID = 0; + } + + /** + * A builder for creating immutable {@code SetMultimap} instances, especially + * {@code public static final} multimaps ("constant multimaps"). Example: + *

   {@code
+   *
+   *   static final Multimap STRING_TO_INTEGER_MULTIMAP =
+   *       new ImmutableSetMultimap.Builder()
+   *           .put("one", 1)
+   *           .putAll("several", 1, 2, 3)
+   *           .putAll("many", 1, 2, 3, 4, 5)
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple multimaps in series. Each multimap + * contains the key-value mappings in the previously created multimaps. + */ + public static final class Builder + extends ImmutableMultimap.Builder { + private final Multimap builderMultimap = new BuilderMultimap(); + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSetMultimap#builder}. + */ + public Builder() {} + + /** + * Adds a key-value mapping to the built multimap if it is not already + * present. + */ + @Override public Builder put(K key, V value) { + builderMultimap.put(checkNotNull(key), checkNotNull(value)); + return this; + } + + /** + * Stores a collection of values with the same key in the built multimap. + * + * @throws NullPointerException if {@code key}, {@code values}, or any + * element in {@code values} is null. The builder is left in an invalid + * state. + */ + @Override public Builder putAll(K key, Iterable values) { + Collection collection = builderMultimap.get(checkNotNull(key)); + for (V value : values) { + collection.add(checkNotNull(value)); + } + return this; + } + + /** + * Stores an array of values with the same key in the built multimap. + * + * @throws NullPointerException if the key or any value is null. The + * builder is left in an invalid state. + */ + @Override public Builder putAll(K key, V... values) { + return putAll(key, Arrays.asList(values)); + } + + /** + * Stores another multimap's entries in the built multimap. The generated + * multimap's key and value orderings correspond to the iteration ordering + * of the {@code multimap.asMap()} view, with new keys and values following + * any existing keys and values. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null. The builder is left in an invalid state. + */ + @Override public Builder putAll( + Multimap multimap) { + for (Map.Entry> entry + : multimap.asMap().entrySet()) { + putAll(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Returns a newly-created immutable set multimap. + */ + @Override public ImmutableSetMultimap build() { + return copyOf(builderMultimap); + } + } + + /** + * Returns an immutable set multimap containing the same mappings as + * {@code multimap}. The generated multimap's key and value orderings + * correspond to the iteration ordering of the {@code multimap.asMap()} view. + * Repeated occurrences of an entry in the multimap after the first are + * ignored. + * + *

Note: Despite what the method name suggests, if + * {@code multimap} is an {@code ImmutableSetMultimap}, no copy will actually + * be performed, and the given multimap itself will be returned. + * + * @throws NullPointerException if any key or value in {@code multimap} is + * null + */ + public static ImmutableSetMultimap copyOf( + Multimap multimap) { + if (multimap.isEmpty()) { + return of(); + } + + if (multimap instanceof ImmutableSetMultimap) { + @SuppressWarnings("unchecked") // safe since multimap is not writable + ImmutableSetMultimap kvMultimap + = (ImmutableSetMultimap) multimap; + return kvMultimap; + } + + ImmutableMap.Builder> builder = ImmutableMap.builder(); + int size = 0; + + for (Map.Entry> entry + : multimap.asMap().entrySet()) { + K key = entry.getKey(); + Collection values = entry.getValue(); + ImmutableSet set = ImmutableSet.copyOf(values); + if (!set.isEmpty()) { + builder.put(key, set); + size += set.size(); + } + } + + return new ImmutableSetMultimap(builder.build(), size); + } + + ImmutableSetMultimap(ImmutableMap> map, int size) { + super(map, size); + } + + // views + + /** + * Returns an immutable set of the values for the given key. If no mappings + * in the multimap have the provided key, an empty immutable set is returned. + * The values are in the same order as the parameters used to build this + * multimap. + */ + @Override public ImmutableSet get(@Nullable K key) { + // This cast is safe as its type is known in constructor. + ImmutableSet set = (ImmutableSet) map.get(key); + return (set == null) ? ImmutableSet.of() : set; + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * Guaranteed to throw an exception and leave the multimap unmodified. + * + * @throws UnsupportedOperationException always + */ + @Override public ImmutableSet replaceValues( + K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + private transient ImmutableSet> entries; + + /** + * Returns an immutable collection of all key-value pairs in the multimap. + * Its iterator traverses the values for the first key, the values for the + * second key, and so on. + */ + // TODO: Fix this so that two copies of the entries are not created. + @Override public ImmutableSet> entries() { + ImmutableSet> result = entries; + return (result == null) + ? (entries = ImmutableSet.copyOf(super.entries())) + : result; + } + + /** + * @serialData number of distinct keys, and then for each distinct key: the + * key, the number of values for that key, and the key's values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultimap(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int keyCount = stream.readInt(); + if (keyCount < 0) { + throw new InvalidObjectException("Invalid key count " + keyCount); + } + ImmutableMap.Builder> builder + = ImmutableMap.builder(); + int tmpSize = 0; + + for (int i = 0; i < keyCount; i++) { + Object key = stream.readObject(); + int valueCount = stream.readInt(); + if (valueCount <= 0) { + throw new InvalidObjectException("Invalid value count " + valueCount); + } + + Object[] array = new Object[valueCount]; + for (int j = 0; j < valueCount; j++) { + array[j] = stream.readObject(); + } + ImmutableSet valueSet = ImmutableSet.of(array); + if (valueSet.size() != array.length) { + throw new InvalidObjectException( + "Duplicate key-value pairs exist for key " + key); + } + builder.put(key, valueSet); + tmpSize += valueCount; + } + + ImmutableMap> tmpMap; + try { + tmpMap = builder.build(); + } catch (IllegalArgumentException e) { + throw (InvalidObjectException) + new InvalidObjectException(e.getMessage()).initCause(e); + } + + FieldSettersHolder.MAP_FIELD_SETTER.set(this, tmpMap); + FieldSettersHolder.SIZE_FIELD_SETTER.set(this, tmpSize); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedMap.java new file mode 100644 index 00000000000..3980d8feff8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedMap.java @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * An immutable {@link SortedMap}. Does not permit null keys or values. + * + *

Unlike {@link Collections#unmodifiableSortedMap}, which is a view + * of a separate map which can still change, an instance of {@code + * ImmutableSortedMap} contains its own data and will never change. + * {@code ImmutableSortedMap} is convenient for {@code public static final} maps + * ("constant maps") and also lets you easily make a "defensive copy" of a map + * provided to your class by a caller. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this class are + * guaranteed to be immutable. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public class ImmutableSortedMap + extends ImmutableSortedMapFauxverideShim implements SortedMap { + + // TODO: Confirm that ImmutableSortedMap is faster to construct and uses less + // memory than TreeMap; then say so in the class Javadoc. + + // TODO: Create separate subclasses for empty, single-entry, and + // multiple-entry instances. + + @SuppressWarnings("unchecked") + private static final Comparator NATURAL_ORDER = Ordering.natural(); + private static final Entry[] EMPTY_ARRAY = new Entry[0]; + + @SuppressWarnings("unchecked") + private static final ImmutableMap NATURAL_EMPTY_MAP + = new ImmutableSortedMap(EMPTY_ARRAY, NATURAL_ORDER); + + /** + * Returns the empty sorted map. + */ + // Casting to any type is safe because the set will never hold any elements. + @SuppressWarnings("unchecked") + public static ImmutableSortedMap of() { + return (ImmutableSortedMap) NATURAL_EMPTY_MAP; + } + + private static ImmutableSortedMap emptyMap( + Comparator comparator) { + if (NATURAL_ORDER.equals(comparator)) { + return ImmutableSortedMap.of(); + } else { + return new ImmutableSortedMap(EMPTY_ARRAY, comparator); + } + } + + /** + * Returns an immutable map containing a single entry. + */ + public static , V> ImmutableSortedMap + of(K k1, V v1) { + Entry[] entries = { entryOf(k1, v1) }; + return new ImmutableSortedMap(entries, Ordering.natural()); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if the two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).build(); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2, K k3, V v3) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).put(k3, v3).build(); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).build(); + } + + /** + * Returns an immutable sorted map containing the given entries, sorted by the + * natural ordering of their keys. + * + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static , V> ImmutableSortedMap + of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return new Builder(Ordering.natural()) + .put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).build(); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, sorted + * by the natural ordering of the keys. + * + *

Note: Despite what the method name suggests, if {@code map} is an + * {@code ImmutableSortedMap}, it may be returned instead of a copy. + * + *

This method is not type-safe, as it may be called on a map with keys + * that are not mutually comparable. + * + * @throws ClassCastException if the keys in {@code map} are not mutually + * comparable + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to + * their natural ordering + */ + public static ImmutableSortedMap copyOf( + Map map) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOfInternal(map, naturalOrder); + } + + /** + * Returns an immutable map containing the same entries as {@code map}, with + * keys sorted by the provided comparator. + * + *

Note: Despite what the method name suggests, if {@code map} is an + * {@code ImmutableSortedMap}, it may be returned instead of a copy. + * + * @throws NullPointerException if any key or value in {@code map} is null + * @throws IllegalArgumentException if any two keys are equal according to + * the comparator + */ + public static ImmutableSortedMap copyOf( + Map map, Comparator comparator) { + return copyOfInternal(map, checkNotNull(comparator)); + } + + /** + * Returns an immutable map containing the same entries as the provided sorted + * map, with the same ordering. + * + *

Note: Despite what the method name suggests, if {@code map} is an + * {@code ImmutableSortedMap}, it may be returned instead of a copy. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + public static ImmutableSortedMap copyOfSorted( + SortedMap map) { + // If map has a null comparator, the keys should have a natural ordering, + // even though K doesn't explicitly implement Comparable. + @SuppressWarnings("unchecked") + Comparator comparator = + (map.comparator() == null) ? NATURAL_ORDER : map.comparator(); + return copyOfInternal(map, comparator); + } + + private static ImmutableSortedMap copyOfInternal( + Map map, Comparator comparator) { + boolean sameComparator = false; + if (map instanceof SortedMap) { + SortedMap sortedMap = (SortedMap) map; + Comparator comparator2 = sortedMap.comparator(); + sameComparator = (comparator2 == null) + ? comparator == NATURAL_ORDER + : comparator.equals(comparator2); + } + + if (sameComparator && (map instanceof ImmutableSortedMap)) { + // TODO: Prove that this cast is safe, even though + // Collections.unmodifiableSortedMap requires the same key type. + @SuppressWarnings("unchecked") + ImmutableSortedMap kvMap = (ImmutableSortedMap) map; + return kvMap; + } + + // Using List to support concurrent map whose size changes + List> list = Lists.newArrayListWithCapacity(map.size()); + for (Entry entry : map.entrySet()) { + list.add(entryOf(entry.getKey(), entry.getValue())); + } + Entry[] entryArray = list.toArray(new Entry[list.size()]); + + if (!sameComparator) { + sortEntries(entryArray, comparator); + validateEntries(entryArray, comparator); + } + + return new ImmutableSortedMap(entryArray, comparator); + } + + private static void sortEntries(Entry[] entryArray, + final Comparator comparator) { + Comparator> entryComparator = new Comparator>() { + public int compare(Entry entry1, Entry entry2) { + return ImmutableSortedSet.unsafeCompare( + comparator, entry1.getKey(), entry2.getKey()); + } + }; + Arrays.sort(entryArray, entryComparator); + } + + private static void validateEntries(Entry[] entryArray, + Comparator comparator) { + for (int i = 1; i < entryArray.length; i++) { + if (ImmutableSortedSet.unsafeCompare(comparator, + entryArray[i - 1].getKey(), entryArray[i].getKey()) == 0) { + throw new IllegalArgumentException( + "Duplicate keys in mappings " + + entryArray[i - 1] + " and " + entryArray[i]); + } + } + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are + * ordered by their natural ordering. The sorted maps use {@link + * Ordering#natural()} as the comparator. + * + *

Note: the type parameter {@code K} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static , V> Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * Returns a builder that creates immutable sorted maps with an explicit + * comparator. If the comparator has a more general type than the map's keys, + * such as creating a {@code SortedMap} with a {@code + * Comparator}, use the {@link Builder} constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted maps whose keys are + * ordered by the reverse of their natural ordering. + * + *

Note: the type parameter {@code K} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static , V> Builder reverseOrder() { + return new Builder(Ordering.natural().reverse()); + } + + /** + * A builder for creating immutable sorted map instances, especially {@code + * public static final} maps ("constant maps"). Example:

   {@code
+   *
+   *   static final ImmutableSortedMap INT_TO_WORD =
+   *       new ImmutableSortedMap.Builder(Ordering.natural())
+   *           .put(1, "one")
+   *           .put(2, "two")
+   *           .put(3, "three")
+   *           .build();}
+ * + * For small immutable sorted maps, the {@code ImmutableSortedMap.of()} + * methods are even more convenient. + * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple maps in series. Each map is a superset of + * the maps created before it. + */ + public static final class Builder extends ImmutableMap.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSortedMap#orderedBy}. + */ + public Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Associates {@code key} with {@code value} in the built map. Duplicate + * keys, according to the comparator (which might be the keys' natural + * order), are not allowed, and will cause {@link #build} to fail. + */ + @Override public Builder put(K key, V value) { + entries.add(entryOf(key, value)); + return this; + } + + /** + * Associates all of the given map's keys and values in the built map. + * Duplicate keys, according to the comparator (which might be the keys' + * natural order), are not allowed, and will cause {@link #build} to fail. + * + * @throws NullPointerException if any key or value in {@code map} is null + */ + @Override public Builder putAll(Map map) { + for (Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * Returns a newly-created immutable sorted map. + * + * @throws IllegalArgumentException if any two keys are equal according to + * the comparator (which might be the keys' natural order) + */ + @Override public ImmutableSortedMap build() { + Entry[] entryArray + = entries.toArray(new Entry[entries.size()]); + sortEntries(entryArray, comparator); + validateEntries(entryArray, comparator); + return new ImmutableSortedMap(entryArray, comparator); + } + } + + private final transient Entry[] entries; + private final transient Comparator comparator; + private final transient int fromIndex; + private final transient int toIndex; + + private ImmutableSortedMap(Entry[] entries, + Comparator comparator, int fromIndex, int toIndex) { + // each of the callers carefully put only Entrys into the array! + @SuppressWarnings("unchecked") + Entry[] tmp = (Entry[]) entries; + this.entries = tmp; + this.comparator = comparator; + this.fromIndex = fromIndex; + this.toIndex = toIndex; + } + + ImmutableSortedMap(Entry[] entries, + Comparator comparator) { + this(entries, comparator, 0, entries.length); + } + + public int size() { + return toIndex - fromIndex; + } + + @Override public V get(@Nullable Object key) { + if (key == null) { + return null; + } + int i; + try { + i = binarySearch(key); + } catch (ClassCastException e) { + return null; + } + return (i >= 0) ? entries[i].getValue() : null; + } + + private int binarySearch(Object key) { + int lower = fromIndex; + int upper = toIndex - 1; + + while (lower <= upper) { + int middle = lower + (upper - lower) / 2; + int c = ImmutableSortedSet.unsafeCompare( + comparator, key, entries[middle].getKey()); + if (c < 0) { + upper = middle - 1; + } else if (c > 0) { + lower = middle + 1; + } else { + return middle; + } + } + + return -lower - 1; + } + + @Override public boolean containsValue(@Nullable Object value) { + if (value == null) { + return false; + } + for (int i = fromIndex; i < toIndex; i++) { + if (entries[i].getValue().equals(value)) { + return true; + } + } + return false; + } + + private transient ImmutableSet> entrySet; + + /** + * Returns an immutable set of the mappings in this map, sorted by the key + * ordering. + */ + @Override public ImmutableSet> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = createEntrySet()) : es; + } + + private ImmutableSet> createEntrySet() { + return isEmpty() ? ImmutableSet.>of() + : new EntrySet(this); + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private static class EntrySet extends ImmutableSet> { + final transient ImmutableSortedMap map; + + EntrySet(ImmutableSortedMap map) { + this.map = map; + } + + public int size() { + return map.size(); + } + + @Override public UnmodifiableIterator> iterator() { + return Iterators.forArray(map.entries, map.fromIndex, size()); + } + + @Override public boolean contains(Object target) { + if (target instanceof Entry) { + Entry entry = (Entry) target; + V mappedValue = map.get(entry.getKey()); + return mappedValue != null && mappedValue.equals(entry.getValue()); + } + return false; + } + + @Override Object writeReplace() { + return new EntrySetSerializedForm(map); + } + } + + private static class EntrySetSerializedForm implements Serializable { + final ImmutableSortedMap map; + EntrySetSerializedForm(ImmutableSortedMap map) { + this.map = map; + } + Object readResolve() { + return map.entrySet(); + } + private static final long serialVersionUID = 0; + } + + private transient ImmutableSortedSet keySet; + + /** + * Returns an immutable sorted set of the keys in this map. + */ + @Override public ImmutableSortedSet keySet() { + ImmutableSortedSet ks = keySet; + return (ks == null) ? (keySet = createKeySet()) : ks; + } + + private ImmutableSortedSet createKeySet() { + if (isEmpty()) { + return ImmutableSortedSet.emptySet(comparator); + } + + // TODO: For better performance, don't create a separate array. + Object[] array = new Object[size()]; + for (int i = fromIndex; i < toIndex; i++) { + array[i - fromIndex] = entries[i].getKey(); + } + return new RegularImmutableSortedSet(array, comparator); + } + + private transient ImmutableCollection values; + + /** + * Returns an immutable collection of the values in this map, sorted by the + * ordering of the corresponding keys. + */ + @Override public ImmutableCollection values() { + ImmutableCollection v = values; + return (v == null) ? (values = new Values(this)) : v; + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private static class Values extends ImmutableCollection { + private final ImmutableSortedMap map; + + Values(ImmutableSortedMap map) { + this.map = map; + } + + public int size() { + return map.size(); + } + + @Override public UnmodifiableIterator iterator() { + return new AbstractIterator() { + int index = map.fromIndex; + @Override protected V computeNext() { + return (index < map.toIndex) + ? map.entries[index++].getValue() + : endOfData(); + } + }; + } + + @Override public boolean contains(Object target) { + return map.containsValue(target); + } + + @Override Object writeReplace() { + return new ValuesSerializedForm(map); + } + } + + private static class ValuesSerializedForm implements Serializable { + final ImmutableSortedMap map; + ValuesSerializedForm(ImmutableSortedMap map) { + this.map = map; + } + Object readResolve() { + return map.values(); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns the comparator that orders the keys, which is + * {@link Ordering#natural()} when the natural ordering of the keys is used. + * Note that its behavior is not consistent with {@link TreeMap#comparator()}, + * which returns {@code null} to indicate natural ordering. + */ + public Comparator comparator() { + return comparator; + } + + public K firstKey() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return entries[fromIndex].getKey(); + } + + public K lastKey() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return entries[toIndex - 1].getKey(); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys are less than {@code toKey}. + * + *

The {@link SortedMap#headMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code toKey} + * greater than an earlier {@code toKey}. However, this method doesn't throw + * an exception in that situation, but instead keeps the original {@code + * toKey}. + */ + public ImmutableSortedMap headMap(K toKey) { + int newToIndex = findSubmapIndex(checkNotNull(toKey)); + return createSubmap(fromIndex, newToIndex); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys ranges from {@code fromKey}, inclusive, to {@code toKey}, + * exclusive. + * + *

The {@link SortedMap#subMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code + * fromKey} less than an earlier {@code fromKey}. However, this method doesn't + * throw an exception in that situation, but instead keeps the original {@code + * fromKey}. Similarly, this method keeps the original {@code toKey}, instead + * of throwing an exception, if passed a {@code toKey} greater than an earlier + * {@code toKey}. + */ + public ImmutableSortedMap subMap(K fromKey, K toKey) { + checkNotNull(fromKey); + checkNotNull(toKey); + checkArgument(comparator.compare(fromKey, toKey) <= 0); + int newFromIndex = findSubmapIndex(fromKey); + int newToIndex = findSubmapIndex(toKey); + return createSubmap(newFromIndex, newToIndex); + } + + /** + * This method returns a {@code ImmutableSortedMap}, consisting of the entries + * whose keys are greater than or equals to {@code fromKey}. + * + *

The {@link SortedMap#tailMap} documentation states that a submap of a + * submap throws an {@link IllegalArgumentException} if passed a {@code + * fromKey} less than an earlier {@code fromKey}. However, this method doesn't + * throw an exception in that situation, but instead keeps the original {@code + * fromKey}. + */ + public ImmutableSortedMap tailMap(K fromKey) { + int newFromIndex = findSubmapIndex(checkNotNull(fromKey)); + return createSubmap(newFromIndex, toIndex); + } + + private int findSubmapIndex(K key) { + int index = binarySearch(key); + return (index >= 0) ? index : (-index - 1); + } + + private ImmutableSortedMap createSubmap( + int newFromIndex, int newToIndex) { + if (newFromIndex < newToIndex) { + return new ImmutableSortedMap(entries, comparator, + newFromIndex, newToIndex); + } else { + return emptyMap(comparator); + } + } + + /** + * Serialized type for all ImmutableSortedMap instances. It captures the + * logical contents and they are reconstructed using public factory methods. + * This ensures that the implementation types remain as implementation + * details. + */ + private static class SerializedForm extends ImmutableMap.SerializedForm { + private final Comparator comparator; + @SuppressWarnings("unchecked") + SerializedForm(ImmutableSortedMap sortedMap) { + super(sortedMap); + comparator = (Comparator) sortedMap.comparator(); + } + @Override Object readResolve() { + Builder builder = new Builder(comparator); + return createMap(builder); + } + private static final long serialVersionUID = 0; + } + + @Override Object writeReplace() { + return new SerializedForm(this); + } + + // This class is never actually serialized directly, but we have to make the + // warning go away (and suppressing would suppress for all nested classes too) + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedMapFauxverideShim.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedMapFauxverideShim.java new file mode 100644 index 00000000000..fbe282816a8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedMapFauxverideShim.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * "Overrides" the {@link ImmutableMap} static methods that lack + * {@link ImmutableSortedMap} equivalents with deprecated, exception-throwing + * versions. See {@link ImmutableSortedSetFauxverideShim} for details. + * + * @author Chris Povirk + */ +@GwtCompatible +abstract class ImmutableSortedMapFauxverideShim + extends ImmutableMap { + /** + * Not supported. Use {@link ImmutableSortedMap#naturalOrder}, which offers + * better type-safety, instead. This method exists only to hide + * {@link ImmutableMap#builder} from consumers of {@code ImmutableSortedMap}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedMap#naturalOrder}, which offers + * better type-safety. + */ + @Deprecated public static ImmutableSortedMap.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain a + * non-{@code Comparable} key. Proper calls will resolve to the version in + * {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a key of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of(K k1, V v1) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls will resolve to the version + * in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls to will resolve to the + * version in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, + * Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls will resolve to the version + * in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a map that may contain + * non-{@code Comparable} keys. Proper calls will resolve to the version + * in {@code ImmutableSortedMap}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass keys of type {@code Comparable} to use {@link + * ImmutableSortedMap#of(Comparable, Object, Comparable, Object, + * Comparable, Object, Comparable, Object, Comparable, Object)}. + */ + @Deprecated public static ImmutableSortedMap of( + K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + throw new UnsupportedOperationException(); + } + + // No copyOf() fauxveride; see ImmutableSortedSetFauxverideShim. +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedSet.java new file mode 100644 index 00000000000..b0b8f4a0898 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedSet.java @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; + +/** + * An immutable {@code SortedSet} that stores its elements in a sorted array. + * Some instances are ordered by an explicit comparator, while others follow the + * natural sort ordering of their elements. Either way, null elements are not + * supported. + * + *

Unlike {@link Collections#unmodifiableSortedSet}, which is a view + * of a separate collection that can still change, an instance of {@code + * ImmutableSortedSet} contains its own private data and will never + * change. This class is convenient for {@code public static final} sets + * ("constant sets") and also lets you easily make a "defensive copy" of a set + * provided to your class by a caller. + * + *

The sets returned by {@link #headSet}, {@link #tailSet}, and + * {@link #subSet} methods share the same array as the original set, preventing + * that array from being garbage collected. If this is a concern, the data may + * be copied into a correctly-sized array by calling {@link #copyOfSorted}. + * + *

Note on element equivalence: The {@link #contains(Object)}, + * {@link #containsAll(Collection)}, and {@link #equals(Object)} + * implementations must check whether a provided object is equivalent to an + * element in the collection. Unlike most collections, an + * {@code ImmutableSortedSet} doesn't use {@link Object#equals} to determine if + * two elements are equivalent. Instead, with an explicit comparator, the + * following relation determines whether elements {@code x} and {@code y} are + * equivalent:

   {@code
+ *
+ *   {(x, y) | comparator.compare(x, y) == 0}}
+ * + * With natural ordering of elements, the following relation determines whether + * two elements are equivalent:
   {@code
+ *
+ *   {(x, y) | x.compareTo(y) == 0}}
+ * + * Warning: Like most sets, an {@code ImmutableSortedSet} will not + * function correctly if an element is modified after being placed in the set. + * For this reason, and to avoid general confusion, it is strongly recommended + * to place only immutable objects into this collection. + * + *

Note: Although this class is not final, it cannot be subclassed as + * it has no public or protected constructors. Thus, instances of this type are + * guaranteed to be immutable. + * + * @see ImmutableSet + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +public abstract class ImmutableSortedSet + extends ImmutableSortedSetFauxverideShim implements SortedSet { + + // TODO: Can we find a way to remove this @SuppressWarnings even for eclipse? + @SuppressWarnings("unchecked") + private static final Comparator NATURAL_ORDER = Ordering.natural(); + + @SuppressWarnings("unchecked") + private static final ImmutableSortedSet NATURAL_EMPTY_SET = + new EmptyImmutableSortedSet(NATURAL_ORDER); + + @SuppressWarnings("unchecked") + private static ImmutableSortedSet emptySet() { + return (ImmutableSortedSet) NATURAL_EMPTY_SET; + } + + static ImmutableSortedSet emptySet( + Comparator comparator) { + if (NATURAL_ORDER.equals(comparator)) { + return emptySet(); + } else { + return new EmptyImmutableSortedSet(comparator); + } + } + + /** + * Returns the empty immutable sorted set. + */ + public static ImmutableSortedSet of() { + return emptySet(); + } + + /** + * Returns an immutable sorted set containing a single element. + */ + public static > ImmutableSortedSet of( + E element) { + Object[] array = { checkNotNull(element) }; + return new RegularImmutableSortedSet(array, Ordering.natural()); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2) { + return ofInternal(Ordering.natural(), e1, e2); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3) { + return ofInternal(Ordering.natural(), e1, e2, e3); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4) { + return ofInternal(Ordering.natural(), e1, e2, e3, e4); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any element is null + */ + @SuppressWarnings("unchecked") + public static > ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5) { + return ofInternal(Ordering.natural(), e1, e2, e3, e4, e5); + } + + // TODO: Consider adding factory methods that throw an exception when given + // duplicate elements. + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@link Comparable#compareTo}, only the first one specified is included. + * + * @throws NullPointerException if any of {@code elements} is null + */ + public static > ImmutableSortedSet of( + E... elements) { + return ofInternal(Ordering.natural(), elements); + } + + private static ImmutableSortedSet ofInternal( + Comparator comparator, E... elements) { + checkNotNull(elements); // for GWT + switch (elements.length) { + case 0: + return emptySet(comparator); + default: + /* + * We can't use Platform.clone() because of GWT bug 3621. See our GWT + * ImmutableSortedSetTest.testOf_gwtArraycopyBug() for details. We can't + * use System.arraycopy() here for the same reason. + */ + Object[] array = new Object[elements.length]; + for (int i = 0; i < elements.length; i++) { + array[i] = checkNotNull(elements[i]); + } + sort(array, comparator); + array = removeDupes(array, comparator); + return new RegularImmutableSortedSet(array, comparator); + } + } + + /** Sort the array, according to the comparator. */ + @SuppressWarnings("unchecked") // E comparator with Object array + private static void sort( + Object[] array, Comparator comparator) { + Arrays.sort(array, (Comparator) comparator); + } + + /** + * Returns an array that removes duplicate consecutive elements, according to + * the provided comparator. Note that the input array is modified. This method + * does not support empty arrays. + */ + private static Object[] removeDupes(Object[] array, + Comparator comparator) { + int size = 1; + for (int i = 1; i < array.length; i++) { + Object element = array[i]; + if (unsafeCompare(comparator, array[size - 1], element) != 0) { + array[size] = element; + size++; + } + } + + // TODO: Move to ObjectArrays? + if (size == array.length) { + return array; + } else { + Object[] copy = new Object[size]; + System.arraycopy(array, 0, copy, 0, size); + return copy; + } + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. To create a + * copy of a {@code SortedSet} that preserves the comparator, call + * {@link #copyOfSorted} instead. This method iterates over {@code elements} + * at most once. + * + *

Note that if {@code s} is a {@code Set}, then + * {@code ImmutableSortedSet.copyOf(s)} returns an + * {@code ImmutableSortedSet} containing each of the strings in + * {@code s}, while {@code ImmutableSortedSet.of(s)} returns an + * {@code ImmutableSortedSet>} containing one element (the given + * set itself). + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableSortedSet}, it may be returned instead of a copy. + * + *

This method is not type-safe, as it may be called on elements that are + * not mutually comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Iterable elements) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOfInternal(naturalOrder, elements, false); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * their natural ordering. When multiple elements are equivalent according to + * {@code compareTo()}, only the first one specified is included. + * + *

This method is not type-safe, as it may be called on elements that are + * not mutually comparable. + * + * @throws ClassCastException if the elements are not mutually comparable + * @throws NullPointerException if any of {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Iterator elements) { + // Hack around K not being a subtype of Comparable. + // Unsafe, see ImmutableSortedSetFauxverideShim. + @SuppressWarnings("unchecked") + Ordering naturalOrder = (Ordering) Ordering.natural(); + return copyOfInternal(naturalOrder, elements); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the given {@code Comparator}. When multiple elements are equivalent + * according to {@code compare()}, only the first one specified is + * included. This method iterates over {@code elements} at most once. + * + *

Note: Despite what the method name suggests, if {@code elements} + * is an {@code ImmutableSortedSet}, it may be returned instead of a copy. + * + * @throws NullPointerException if {@code comparator} or any of + * {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterable elements) { + checkNotNull(comparator); + return copyOfInternal(comparator, elements, false); + } + + /** + * Returns an immutable sorted set containing the given elements sorted by + * the given {@code Comparator}. When multiple elements are equivalent + * according to {@code compareTo()}, only the first one specified is + * included. + * + * @throws NullPointerException if {@code comparator} or any of + * {@code elements} is null + */ + public static ImmutableSortedSet copyOf( + Comparator comparator, Iterator elements) { + checkNotNull(comparator); + return copyOfInternal(comparator, elements); + } + + /** + * Returns an immutable sorted set containing the elements of a sorted set, + * sorted by the same {@code Comparator}. That behavior differs from + * {@link #copyOf(Iterable)}, which always uses the natural ordering of the + * elements. + * + *

Note: Despite what the method name suggests, if {@code sortedSet} + * is an {@code ImmutableSortedSet}, it may be returned instead of a copy. + * + * @throws NullPointerException if any of {@code elements} is null + */ + @SuppressWarnings("unchecked") + public static ImmutableSortedSet copyOfSorted(SortedSet sortedSet) { + Comparator comparator = sortedSet.comparator(); + if (comparator == null) { + comparator = NATURAL_ORDER; + } + return copyOfInternal(comparator, sortedSet, true); + } + + private static ImmutableSortedSet copyOfInternal( + Comparator comparator, Iterable elements, + boolean fromSortedSet) { + boolean hasSameComparator + = fromSortedSet || hasSameComparator(elements, comparator); + + if (hasSameComparator && (elements instanceof ImmutableSortedSet)) { + @SuppressWarnings("unchecked") + ImmutableSortedSet result = (ImmutableSortedSet) elements; + if (!result.hasPartialArray()) { + return result; + } + } + + Object[] array = newObjectArray(elements); + if (array.length == 0) { + return emptySet(comparator); + } + + for (Object e : array) { + checkNotNull(e); + } + if (!hasSameComparator) { + sort(array, comparator); + array = removeDupes(array, comparator); + } + return new RegularImmutableSortedSet(array, comparator); + } + + /** Simplified version of {@link Iterables#toArray} that is GWT safe. */ + private static Object[] newObjectArray(Iterable iterable) { + Collection collection = (iterable instanceof Collection) + ? (Collection) iterable : Lists.newArrayList(iterable); + Object[] array = new Object[collection.size()]; + return collection.toArray(array); + } + + private static ImmutableSortedSet copyOfInternal( + Comparator comparator, Iterator elements) { + if (!elements.hasNext()) { + return emptySet(comparator); + } + List list = Lists.newArrayList(); + while (elements.hasNext()) { + list.add(checkNotNull(elements.next())); + } + Object[] array = list.toArray(); + sort(array, comparator); + array = removeDupes(array, comparator); + return new RegularImmutableSortedSet(array, comparator); + } + + /** + * Returns {@code true} if {@code elements} is a {@code SortedSet} that uses + * {@code comparator} to order its elements. Note that equivalent comparators + * may still return {@code false}, if {@code equals} doesn't consider them + * equal. If one comparator is {@code null} and the other is + * {@link Ordering#natural()}, this method returns {@code true}. + */ + static boolean hasSameComparator( + Iterable elements, Comparator comparator) { + if (elements instanceof SortedSet) { + SortedSet sortedSet = (SortedSet) elements; + Comparator comparator2 = sortedSet.comparator(); + return (comparator2 == null) + ? comparator == Ordering.natural() + : comparator.equals(comparator2); + } + return false; + } + + /** + * Returns a builder that creates immutable sorted sets with an explicit + * comparator. If the comparator has a more general type than the set being + * generated, such as creating a {@code SortedSet} with a + * {@code Comparator}, use the {@link Builder} constructor instead. + * + * @throws NullPointerException if {@code comparator} is null + */ + public static Builder orderedBy(Comparator comparator) { + return new Builder(comparator); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are + * ordered by the reverse of their natural ordering. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static > Builder reverseOrder() { + return new Builder(Ordering.natural().reverse()); + } + + /** + * Returns a builder that creates immutable sorted sets whose elements are + * ordered by their natural ordering. The sorted sets use {@link + * Ordering#natural()} as the comparator. This method provides more + * type-safety than {@link #builder}, as it can be called only for classes + * that implement {@link Comparable}. + * + *

Note: the type parameter {@code E} extends {@code Comparable} rather + * than {@code Comparable} as a workaround for javac bug + * 6468354. + */ + public static > Builder naturalOrder() { + return new Builder(Ordering.natural()); + } + + /** + * A builder for creating immutable sorted set instances, especially + * {@code public static final} sets ("constant sets"), with a given + * comparator. + * + *

Example: + *

{@code
+   *   public static final ImmutableSortedSet LUCKY_NUMBERS
+   *       = new ImmutableSortedSet.Builder(ODDS_FIRST_COMPARATOR)
+   *           .addAll(SINGLE_DIGIT_PRIMES)
+   *           .add(42)
+   *           .build();}
+ * + *

Builder instances can be reused - it is safe to call {@link #build} + * multiple times to build multiple sets in series. Each set + * is a superset of the set created before it. + */ + public static final class Builder extends ImmutableSet.Builder { + private final Comparator comparator; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ImmutableSortedSet#orderedBy}. + */ + public Builder(Comparator comparator) { + this.comparator = checkNotNull(comparator); + } + + /** + * Adds {@code element} to the {@code ImmutableSortedSet}. If the + * {@code ImmutableSortedSet} already contains {@code element}, then + * {@code add} has no effect. (only the previously added element + * is retained). + * + * @param element the element to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code element} is null + */ + @Override public Builder add(E element) { + super.add(element); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override public Builder add(E... elements) { + super.add(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override public Builder addAll(Iterable elements) { + super.addAll(elements); + return this; + } + + /** + * Adds each element of {@code elements} to the {@code ImmutableSortedSet}, + * ignoring duplicate elements (only the first duplicate element is added). + * + * @param elements the elements to add to the {@code ImmutableSortedSet} + * @return this {@code Builder} object + * @throws NullPointerException if {@code elements} contains a null element + */ + @Override public Builder addAll(Iterator elements) { + super.addAll(elements); + return this; + } + + /** + * Returns a newly-created {@code ImmutableSortedSet} based on the contents + * of the {@code Builder} and its comparator. + */ + @Override public ImmutableSortedSet build() { + return copyOfInternal(comparator, contents.iterator()); + } + } + + int unsafeCompare(Object a, Object b) { + return unsafeCompare(comparator, a, b); + } + + static int unsafeCompare( + Comparator comparator, Object a, Object b) { + // Pretend the comparator can compare anything. If it turns out it can't + // compare a and b, we should get a CCE on the subsequent line. Only methods + // that are spec'd to throw CCE should call this. + @SuppressWarnings("unchecked") + Comparator unsafeComparator = (Comparator) comparator; + return unsafeComparator.compare(a, b); + } + + final transient Comparator comparator; + + ImmutableSortedSet(Comparator comparator) { + this.comparator = comparator; + } + + /** + * Returns the comparator that orders the elements, which is + * {@link Ordering#natural()} when the natural ordering of the + * elements is used. Note that its behavior is not consistent with + * {@link SortedSet#comparator()}, which returns {@code null} to indicate + * natural ordering. + */ + public Comparator comparator() { + return comparator; + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#headSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code toElement} greater than an earlier {@code toElement}. However, this + * method doesn't throw an exception in that situation, but instead keeps the + * original {@code toElement}. + */ + public ImmutableSortedSet headSet(E toElement) { + return headSetImpl(checkNotNull(toElement)); + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#subSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code fromElement} smaller than an earlier {@code fromElement}. However, + * this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromElement}. Similarly, this method keeps the + * original {@code toElement}, instead of throwing an exception, if passed a + * {@code toElement} greater than an earlier {@code toElement}. + */ + public ImmutableSortedSet subSet(E fromElement, E toElement) { + checkNotNull(fromElement); + checkNotNull(toElement); + checkArgument(comparator.compare(fromElement, toElement) <= 0); + return subSetImpl(fromElement, toElement); + } + + /** + * {@inheritDoc} + * + *

This method returns a serializable {@code ImmutableSortedSet}. + * + *

The {@link SortedSet#tailSet} documentation states that a subset of a + * subset throws an {@link IllegalArgumentException} if passed a + * {@code fromElement} smaller than an earlier {@code fromElement}. However, + * this method doesn't throw an exception in that situation, but instead keeps + * the original {@code fromElement}. + */ + public ImmutableSortedSet tailSet(E fromElement) { + return tailSetImpl(checkNotNull(fromElement)); + } + + /* + * These methods perform most headSet, subSet, and tailSet logic, besides + * parameter validation. + */ + abstract ImmutableSortedSet headSetImpl(E toElement); + abstract ImmutableSortedSet subSetImpl(E fromElement, E toElement); + abstract ImmutableSortedSet tailSetImpl(E fromElement); + + /** Returns whether the elements are stored in a subset of a larger array. */ + abstract boolean hasPartialArray(); + + /* + * This class is used to serialize all ImmutableSortedSet instances, + * regardless of implementation type. It captures their "logical contents" + * only. This is necessary to ensure that the existence of a particular + * implementation type is an implementation detail. + */ + private static class SerializedForm implements Serializable { + final Comparator comparator; + final Object[] elements; + + public SerializedForm(Comparator comparator, Object[] elements) { + this.comparator = comparator; + this.elements = elements; + } + + @SuppressWarnings("unchecked") + Object readResolve() { + return new Builder(comparator).add((E[]) elements).build(); + } + + private static final long serialVersionUID = 0; + } + + private void readObject(ObjectInputStream stream) + throws InvalidObjectException { + throw new InvalidObjectException("Use SerializedForm"); + } + + @Override Object writeReplace() { + return new SerializedForm(comparator, toArray()); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedSetFauxverideShim.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedSetFauxverideShim.java new file mode 100644 index 00000000000..fb76120b8e7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ImmutableSortedSetFauxverideShim.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * "Overrides" the {@link ImmutableSet} static methods that lack + * {@link ImmutableSortedSet} equivalents with deprecated, exception-throwing + * versions. This prevents accidents like the following:

   {@code
+ *
+ *   List objects = ...;
+ *   // Sort them:
+ *   Set sorted = ImmutableSortedSet.copyOf(objects);
+ *   // BAD CODE! The returned set is actually an unsorted ImmutableSet!}
+ *
+ * 

While we could put the overrides in {@link ImmutableSortedSet} itself, it + * seems clearer to separate these "do not call" methods from those intended for + * normal use. + * + * @author Chris Povirk + */ +@GwtCompatible +abstract class ImmutableSortedSetFauxverideShim extends ImmutableSet { + /** + * Not supported. Use {@link ImmutableSortedSet#naturalOrder}, which offers + * better type-safety, instead. This method exists only to hide + * {@link ImmutableSet#builder} from consumers of {@code ImmutableSortedSet}. + * + * @throws UnsupportedOperationException always + * @deprecated Use {@link ImmutableSortedSet#naturalOrder}, which offers + * better type-safety. + */ + @Deprecated public static ImmutableSortedSet.Builder builder() { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a + * non-{@code Comparable} element. Proper calls will resolve to the + * version in {@code ImmutableSortedSet}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass a parameter of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable)}. + */ + @Deprecated public static ImmutableSortedSet of(E element) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a + * non-{@code Comparable} element. Proper calls will resolve to the + * version in {@code ImmutableSortedSet}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable)}. + */ + @Deprecated public static ImmutableSortedSet of(E e1, E e2) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a + * non-{@code Comparable} element. Proper calls will resolve to the + * version in {@code ImmutableSortedSet}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable)}. + */ + @Deprecated public static ImmutableSortedSet of(E e1, E e2, E e3) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a + * non-{@code Comparable} element. Proper calls will resolve to the + * version in {@code ImmutableSortedSet}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable, Comparable, Comparable, Comparable)}. + * + */ + @Deprecated public static ImmutableSortedSet of( + E e1, E e2, E e3, E e4) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain a + * non-{@code Comparable} element. Proper calls will resolve to the + * version in {@code ImmutableSortedSet}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass the parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of( + * Comparable, Comparable, Comparable, Comparable, Comparable)}. + */ + @Deprecated public static ImmutableSortedSet of( + E e1, E e2, E e3, E e4, E e5) { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. You are attempting to create a set that may contain + * non-{@code Comparable} elements. Proper calls will resolve to the + * version in {@code ImmutableSortedSet}, not this dummy version. + * + * @throws UnsupportedOperationException always + * @deprecated Pass parameters of type {@code Comparable} to use {@link + * ImmutableSortedSet#of(Comparable[])}. + */ + @Deprecated public static ImmutableSortedSet of(E... elements) { + throw new UnsupportedOperationException(); + } + + /* + * We would like to include an unsupported " copyOf(Iterable)" here, + * providing only the properly typed + * "> copyOf(Iterable)" in ImmutableSortedSet (and + * likewise for the Iterator equivalent). However, due to a change in Sun's + * interpretation of the JLS (as described at + * http://bugs.sun.com/view_bug.do?bug_id=6182950), the OpenJDK 7 compiler + * available as of this writing rejects our attempts. To maintain + * compatibility with that version and with any other compilers that interpret + * the JLS similarly, there is no definition of copyOf() here, and the + * definition in ImmutableSortedSet matches that in ImmutableSet. + * + * The result is that ImmutableSortedSet.copyOf() may be called on + * non-Comparable elements. We have not discovered a better solution. In + * retrospect, the static factory methods should have gone in a separate class + * so that ImmutableSortedSet wouldn't "inherit" too-permissive factory + * methods from ImmutableSet. + */ +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Iterables.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Iterables.java new file mode 100644 index 00000000000..a087cb250ac --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Iterables.java @@ -0,0 +1,717 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.base.Objects; +import org.elasticsearch.util.gcommon.base.Preconditions; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import org.elasticsearch.util.gcommon.base.Predicate; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * This class contains static utility methods that operate on or return objects + * of type {@code Iterable}. Except as noted, each method has a corresponding + * {@link Iterator}-based method in the {@link Iterators} class. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +@GwtCompatible +public final class Iterables { + private Iterables() {} + + /** Returns an unmodifiable view of {@code iterable}. */ + public static Iterable unmodifiableIterable(final Iterable iterable) + { + checkNotNull(iterable); + return new Iterable() { + public Iterator iterator() { + return Iterators.unmodifiableIterator(iterable.iterator()); + } + @Override public String toString() { + return iterable.toString(); + } + // no equals and hashCode; it would break the contract! + }; + } + + /** + * Returns the number of elements in {@code iterable}. + */ + public static int size(Iterable iterable) { + return (iterable instanceof Collection) + ? ((Collection) iterable).size() + : Iterators.size(iterable.iterator()); + } + + /** + * Returns {@code true} if {@code iterable} contains {@code element}; that is, + * any object for while {@code equals(element)} is true. + */ + public static boolean contains(Iterable iterable, @Nullable Object element) + { + if (iterable instanceof Collection) { + Collection collection = (Collection) iterable; + try { + return collection.contains(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + return Iterators.contains(iterable.iterator(), element); + } + + /** + * Removes, from an iterable, every element that belongs to the provided + * collection. + * + *

This method calls {@link Collection#removeAll} if {@code iterable} is a + * collection, and {@link Iterators#removeAll} otherwise. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param elementsToRemove the elements to remove + * @return {@code true} if any elements are removed from {@code iterable} + */ + public static boolean removeAll( + Iterable removeFrom, Collection elementsToRemove) { + return (removeFrom instanceof Collection) + ? ((Collection) removeFrom).removeAll(checkNotNull(elementsToRemove)) + : Iterators.removeAll(removeFrom.iterator(), elementsToRemove); + } + + /** + * Removes, from an iterable, every element that does not belong to the + * provided collection. + * + *

This method calls {@link Collection#retainAll} if {@code iterable} is a + * collection, and {@link Iterators#retainAll} otherwise. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param elementsToRetain the elements to retain + * @return {@code true} if any elements are removed from {@code iterable} + */ + public static boolean retainAll( + Iterable removeFrom, Collection elementsToRetain) { + return (removeFrom instanceof Collection) + ? ((Collection) removeFrom).retainAll(checkNotNull(elementsToRetain)) + : Iterators.retainAll(removeFrom.iterator(), elementsToRetain); + } + + /** + * Removes, from an iterable, every element that satisfies the provided + * predicate. + * + * @param removeFrom the iterable to (potentially) remove elements from + * @param predicate a predicate that determines whether an element should + * be removed + * @return {@code true} if any elements were removed from the iterable + * + * @throws UnsupportedOperationException if the iterable does not support + * {@code remove()}. + */ + static boolean removeIf( + Iterable removeFrom, Predicate predicate) { + if (removeFrom instanceof RandomAccess && removeFrom instanceof List) { + return removeIfFromRandomAccessList( + (List) removeFrom, checkNotNull(predicate)); + } + return Iterators.removeIf(removeFrom.iterator(), predicate); + } + + private static boolean removeIfFromRandomAccessList( + List list, Predicate predicate) { + int from = 0; + int to = 0; + + for (; from < list.size(); from++) { + T element = list.get(from); + if (!predicate.apply(element)) { + if (from > to) { + list.set(to, element); + } + to++; + } + } + + // Clear the tail of any remaining items + // Note: hand-written GWT-compatible version of + // list.subList(to, list.size()).clear(); + ListIterator iter = list.listIterator(list.size()); + for (int idx = from - to; idx > 0; idx--) { + iter.previous(); + iter.remove(); + } + + return from != to; + } + + /** + * Determines whether two iterables contain equal elements in the same order. + * More specifically, this method returns {@code true} if {@code iterable1} + * and {@code iterable2} contain the same number of elements and every element + * of {@code iterable1} is equal to the corresponding element of + * {@code iterable2}. + */ + public static boolean elementsEqual( + Iterable iterable1, Iterable iterable2) { + return Iterators.elementsEqual(iterable1.iterator(), iterable2.iterator()); + } + + /** + * Returns a string representation of {@code iterable}, with the format + * {@code [e1, e2, ..., en]}. + */ + public static String toString(Iterable iterable) { + return Iterators.toString(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}. + * + * @throws NoSuchElementException if the iterable is empty + * @throws IllegalArgumentException if the iterable contains multiple + * elements + */ + public static T getOnlyElement(Iterable iterable) { + return Iterators.getOnlyElement(iterable.iterator()); + } + + /** + * Returns the single element contained in {@code iterable}, or {@code + * defaultValue} if the iterable is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple + * elements + */ + public static T getOnlyElement( + Iterable iterable, @Nullable T defaultValue) { + return Iterators.getOnlyElement(iterable.iterator(), defaultValue); + } + + /** + * Copies an iterable's elements into an array. + * + * @param iterable the iterable to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterable + * have been copied + */ + @GwtIncompatible("Array.newInstance(Class, int)") + public static T[] toArray(Iterable iterable, Class type) { + @SuppressWarnings("unchecked") // bugs.sun.com/view_bug.do?bug_id=6558557 + Collection collection = (iterable instanceof Collection) + ? (Collection) iterable + : Lists.newArrayList(iterable); + T[] array = ObjectArrays.newArray(type, collection.size()); + return collection.toArray(array); + } + + /** + * Adds all elements in {@code iterable} to {@code collection}. + * + * @return {@code true} if {@code collection} was modified as a result of this + * operation. + */ + public static boolean addAll( + Collection addTo, Iterable elementsToAdd) { + if (elementsToAdd instanceof Collection) { + @SuppressWarnings("unchecked") + Collection c = (Collection) elementsToAdd; + return addTo.addAll(c); + } + return Iterators.addAll(addTo, elementsToAdd.iterator()); + } + + /** + * Returns the number of elements in the specified iterable that equal the + * specified object. + * + * @see Collections#frequency + */ + public static int frequency(Iterable iterable, @Nullable Object element) { + if ((iterable instanceof Multiset)) { + return ((Multiset) iterable).count(element); + } + if ((iterable instanceof Set)) { + return ((Set) iterable).contains(element) ? 1 : 0; + } + return Iterators.frequency(iterable.iterator(), element); + } + + /** + * Returns an iterable whose iterators cycle indefinitely over the elements of + * {@code iterable}. + * + *

That iterator supports {@code remove()} if {@code iterable.iterator()} + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, which is no longer in {@code iterable}. The iterator's + * {@code hasNext()} method returns {@code true} until {@code iterable} is + * empty. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + * + *

To cycle over the iterable {@code n} times, use the following: + * {@code Iterables.concat(Collections.nCopies(n, iterable))} + */ + public static Iterable cycle(final Iterable iterable) { + checkNotNull(iterable); + return new Iterable() { + public Iterator iterator() { + return Iterators.cycle(iterable); + } + @Override public String toString() { + return iterable.toString() + " (cycled)"; + } + }; + } + + /** + * Returns an iterable whose iterators cycle indefinitely over the provided + * elements. + * + *

After {@code remove} is invoked on a generated iterator, the removed + * element will no longer appear in either that iterator or any other iterator + * created from the same source iterable. That is, this method behaves exactly + * as {@code Iterables.cycle(Lists.newArrayList(elements))}. The iterator's + * {@code hasNext} method returns {@code true} until all of the original + * elements have been removed. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + * + *

To cycle over the elements {@code n} times, use the following: + * {@code Iterables.concat(Collections.nCopies(n, Arrays.asList(elements)))} + */ + public static Iterable cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterables into a single iterable. The returned iterable has an + * iterator that traverses the elements in {@code a}, followed by the elements + * in {@code b}. The source iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterable concat( + Iterable a, Iterable b) { + checkNotNull(a); + checkNotNull(b); + return concat(Arrays.asList(a, b)); + } + + /** + * Combines three iterables into a single iterable. The returned iterable has + * an iterator that traverses the elements in {@code a}, followed by the + * elements in {@code b}, followed by the elements in {@code c}. The source + * iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterable concat(Iterable a, + Iterable b, Iterable c) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + return concat(Arrays.asList(a, b, c)); + } + + /** + * Combines four iterables into a single iterable. The returned iterable has + * an iterator that traverses the elements in {@code a}, followed by the + * elements in {@code b}, followed by the elements in {@code c}, followed by + * the elements in {@code d}. The source iterators are not polled until + * necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterable concat(Iterable a, + Iterable b, Iterable c, + Iterable d) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + checkNotNull(d); + return concat(Arrays.asList(a, b, c, d)); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable + * has an iterator that traverses the elements of each iterable in + * {@code inputs}. The input iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + * + * @throws NullPointerException if any of the provided iterables is null + */ + public static Iterable concat(Iterable... inputs) { + return concat(ImmutableList.of(inputs)); + } + + /** + * Combines multiple iterables into a single iterable. The returned iterable + * has an iterator that traverses the elements of each iterable in + * {@code inputs}. The input iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. The methods of the returned + * iterable may throw {@code NullPointerException} if any of the input + * iterators are null. + */ + public static Iterable concat( + Iterable> inputs) { + /* + * Hint: if you let A represent Iterable and B represent + * Iterator, then this Function would look simply like: + * + * Function function = new Function { + * public B apply(A from) { + * return from.iterator(); + * } + * } + * + * TODO: there may be a better way to do this. + */ + + Function, Iterator> function + = new Function, Iterator>() { + public Iterator apply(Iterable from) { + return from.iterator(); + } + }; + final Iterable> iterators + = transform(inputs, function); + return new IterableWithToString() { + public Iterator iterator() { + return Iterators.concat(iterators.iterator()); + } + }; + } + + /** + * Divides an iterable into unmodifiable sublists of the given size (the final + * iterable may be smaller). For example, partitioning an iterable containing + * {@code [a, b, c, d, e]} with a partition size of 3 yields {@code + * [[a, b, c], [d, e]]} -- an outer iterable containing two inner lists of + * three and two elements, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link + * Iterator#remove()} method. The returned lists implement {@link + * RandomAccess}, whether or not the input list does. + * + *

Note: if {@code iterable} is a {@link List}, use {@link + * Lists#partition(List, int)} instead. + * + * @param iterable the iterable to return a partitioned view of + * @param size the desired size of each partition (the last may be smaller) + * @return an iterable of unmodifiable lists containing the elements of {@code + * iterable} divided into partitions + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static Iterable> partition( + final Iterable iterable, final int size) { + checkNotNull(iterable); + checkArgument(size > 0); + return new IterableWithToString>() { + public Iterator> iterator() { + return Iterators.partition(iterable.iterator(), size); + } + }; + } + + /** + * Divides an iterable into unmodifiable sublists of the given size, padding + * the final iterable with null values if necessary. For example, partitioning + * an iterable containing {@code [a, b, c, d, e]} with a partition size of 3 + * yields {@code [[a, b, c], [d, e, null]]} -- an outer iterable containing + * two inner lists of three elements each, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link + * Iterator#remove()} method. + * + * @param iterable the iterable to return a partitioned view of + * @param size the desired size of each partition + * @return an iterable of unmodifiable lists containing the elements of {@code + * iterable} divided into partitions (the final iterable may have + * trailing null elements) + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static Iterable> paddedPartition( + final Iterable iterable, final int size) { + checkNotNull(iterable); + checkArgument(size > 0); + return new IterableWithToString>() { + public Iterator> iterator() { + return Iterators.paddedPartition(iterable.iterator(), size); + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * resulting iterable's iterator does not support {@code remove()}. + */ + public static Iterable filter( + final Iterable unfiltered, final Predicate predicate) { + checkNotNull(unfiltered); + checkNotNull(predicate); + return new IterableWithToString() { + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), predicate); + } + }; + } + + /** + * Returns all instances of class {@code type} in {@code unfiltered}. The + * returned iterable has elements whose class is {@code type} or a subclass of + * {@code type}. The returned iterable's iterator does not support + * {@code remove()}. + * + * @param unfiltered an iterable containing objects of any type + * @param type the type of elements desired + * @return an unmodifiable iterable containing all elements of the original + * iterable that were of the requested type + */ + @GwtIncompatible("Class.isInstance") + public static Iterable filter( + final Iterable unfiltered, final Class type) { + checkNotNull(unfiltered); + checkNotNull(type); + return new IterableWithToString() { + public Iterator iterator() { + return Iterators.filter(unfiltered.iterator(), type); + } + }; + } + + /** + * Returns {@code true} if one or more elements in {@code iterable} satisfy + * the predicate. + */ + public static boolean any( + Iterable iterable, Predicate predicate) { + return Iterators.any(iterable.iterator(), predicate); + } + + /** + * Returns {@code true} if every element in {@code iterable} satisfies the + * predicate. If {@code iterable} is empty, {@code true} is returned. + */ + public static boolean all( + Iterable iterable, Predicate predicate) { + return Iterators.all(iterable.iterator(), predicate); + } + + /** + * Returns the first element in {@code iterable} that satisfies the given + * predicate. + * + * @throws NoSuchElementException if no element in {@code iterable} matches + * the given predicate + */ + public static T find(Iterable iterable, + Predicate predicate) { + return Iterators.find(iterable.iterator(), predicate); + } + + /** + * Returns an iterable that applies {@code function} to each element of {@code + * fromIterable}. + * + *

The returned iterable's iterator supports {@code remove()} if the + * provided iterator does. After a successful {@code remove()} call, + * {@code fromIterable} no longer contains the corresponding element. + */ + public static Iterable transform(final Iterable fromIterable, + final Function function) { + checkNotNull(fromIterable); + checkNotNull(function); + return new IterableWithToString() { + public Iterator iterator() { + return Iterators.transform(fromIterable.iterator(), function); + } + }; + } + + /** + * Returns the element at the specified position in an iterable. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterable} + * @throws IndexOutOfBoundsException if {@code position} is negative or + * greater than or equal to the size of {@code iterable} + */ + public static T get(Iterable iterable, int position) { + checkNotNull(iterable); + if (iterable instanceof List) { + return ((List) iterable).get(position); + } + + if (iterable instanceof Collection) { + // Can check both ends + Collection collection = (Collection) iterable; + Preconditions.checkElementIndex(position, collection.size()); + } else { + // Can only check the lower end + if (position < 0) { + throw new IndexOutOfBoundsException( + "position cannot be negative: " + position); + } + } + return Iterators.get(iterable.iterator(), position); + } + + /** + * Returns the last element of {@code iterable}. + * + * @return the last element of {@code iterable} + * @throws NoSuchElementException if the iterable has no elements + */ + public static T getLast(Iterable iterable) { + if (iterable instanceof List) { + List list = (List) iterable; + // TODO: Support a concurrent list whose size changes while this method + // is running. + if (list.isEmpty()) { + throw new NoSuchElementException(); + } + return list.get(list.size() - 1); + } + + if (iterable instanceof SortedSet) { + SortedSet sortedSet = (SortedSet) iterable; + return sortedSet.last(); + } + + return Iterators.getLast(iterable.iterator()); + } + + // Methods only in Iterables, not in Iterators + + /** + * Adapts a list to an iterable with reversed iteration order. It is + * especially useful in foreach-style loops:

   {@code
+   *
+   *   List mylist = ...
+   *   for (String str : Iterables.reverse(mylist)) {
+   *     ...
+   *   }}
+ * + * There is no corresponding method in {@link Iterators}, since {@link + * Iterable#iterator} can simply be invoked on the result of calling this + * method. + * + * @return an iterable with the same elements as the list, in reverse + */ + public static Iterable reverse(final List list) { + checkNotNull(list); + return new IterableWithToString() { + public Iterator iterator() { + final ListIterator listIter = list.listIterator(list.size()); + return new Iterator() { + public boolean hasNext() { + return listIter.hasPrevious(); + } + public T next() { + return listIter.previous(); + } + public void remove() { + listIter.remove(); + } + }; + } + }; + } + + /** + * Determines if the given iterable contains no elements. + * + *

There is no precise {@link Iterator} equivalent to this method, since + * one can only ask an iterator whether it has any elements remaining + * (which one does using {@link Iterator#hasNext}). + * + * @return {@code true} if the iterable contains no elements + */ + public static boolean isEmpty(Iterable iterable) { + return !iterable.iterator().hasNext(); + } + + /** + * Removes the specified element from the specified iterable. + * + *

This method iterates over the iterable, checking each element returned + * by the iterator in turn to see if it equals the object {@code o}. If they + * are equal, it is removed from the iterable with the iterator's + * {@code remove} method. At most one element is removed, even if the iterable + * contains multiple members that equal {@code o}. + * + *

Warning: Do not use this method for a collection, such as a + * {@link HashSet}, that has a fast {@code remove} method. + * + * @param iterable the iterable from which to remove + * @param o an element to remove from the collection + * @return {@code true} if the iterable changed as a result + * @throws UnsupportedOperationException if the iterator does not support the + * {@code remove} method and the iterable contains the object + */ + static boolean remove(Iterable iterable, @Nullable Object o) { + Iterator i = iterable.iterator(); + while (i.hasNext()) { + if (Objects.equal(i.next(), o)) { + i.remove(); + return true; + } + } + return false; + } + + abstract static class IterableWithToString implements Iterable { + @Override public String toString() { + return Iterables.toString(this); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Iterators.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Iterators.java new file mode 100644 index 00000000000..34f2621ff24 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Iterators.java @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.base.Objects; +import org.elasticsearch.util.gcommon.base.Preconditions; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; +import org.elasticsearch.util.gcommon.base.Predicate; +import org.elasticsearch.util.gcommon.base.Predicates; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * This class contains static utility methods that operate on or return objects + * of type {@link Iterator}. Except as noted, each method has a corresponding + * {@link Iterable}-based method in the {@link Iterables} class. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +@GwtCompatible +public final class Iterators { + private Iterators() {} + + static final UnmodifiableIterator EMPTY_ITERATOR + = new UnmodifiableIterator() { + public boolean hasNext() { + return false; + } + public Object next() { + throw new NoSuchElementException(); + } + }; + + /** + * Returns the empty iterator. + * + *

The {@link Iterable} equivalent of this method is {@link + * Collections#emptySet}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + public static UnmodifiableIterator emptyIterator() { + return (UnmodifiableIterator) EMPTY_ITERATOR; + } + + private static final Iterator EMPTY_MODIFIABLE_ITERATOR = + new Iterator() { + /*@Override*/ public boolean hasNext() { + return false; + } + + /*@Override*/ public Object next() { + throw new NoSuchElementException(); + } + + /*@Override*/ public void remove() { + throw new IllegalStateException(); + } + }; + + /** + * Returns the empty {@code Iterator} that throws + * {@link IllegalStateException} instead of + * {@link UnsupportedOperationException} on a call to + * {@link Iterator#remove()}. + */ + // Casting to any type is safe since there are no actual elements. + @SuppressWarnings("unchecked") + static Iterator emptyModifiableIterator() { + return (Iterator) EMPTY_MODIFIABLE_ITERATOR; + } + + /** Returns an unmodifiable view of {@code iterator}. */ + public static UnmodifiableIterator unmodifiableIterator( + final Iterator iterator) { + checkNotNull(iterator); + return new UnmodifiableIterator() { + public boolean hasNext() { + return iterator.hasNext(); + } + public T next() { + return iterator.next(); + } + }; + } + + /** + * Returns the number of elements remaining in {@code iterator}. The iterator + * will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + */ + public static int size(Iterator iterator) { + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return count; + } + + /** + * Returns {@code true} if {@code iterator} contains {@code element}. + */ + public static boolean contains(Iterator iterator, @Nullable Object element) + { + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + return true; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + return true; + } + } + } + return false; + } + + /** + * Traverses an iterator and removes every element that belongs to the + * provided collection. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param elementsToRemove the elements to remove + * @return {@code true} if any elements are removed from {@code iterator} + */ + public static boolean removeAll( + Iterator removeFrom, Collection elementsToRemove) { + checkNotNull(elementsToRemove); + boolean modified = false; + while (removeFrom.hasNext()) { + if (elementsToRemove.contains(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Removes every element that satisfies the provided predicate from the + * iterator. The iterator will be left exhausted: its {@code hasNext()} + * method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param predicate a predicate that determines whether an element should + * be removed + * @return {@code true} if any elements were removed from the iterator + */ + // TODO: make public post-1.0 + static boolean removeIf( + Iterator removeFrom, Predicate predicate) { + checkNotNull(predicate); + boolean modified = false; + while (removeFrom.hasNext()) { + if (predicate.apply(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Traverses an iterator and removes every element that does not belong to the + * provided collection. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @param removeFrom the iterator to (potentially) remove elements from + * @param elementsToRetain the elements to retain + * @return {@code true} if any elements are removed from {@code iterator} + */ + public static boolean retainAll( + Iterator removeFrom, Collection elementsToRetain) { + checkNotNull(elementsToRetain); + boolean modified = false; + while (removeFrom.hasNext()) { + if (!elementsToRetain.contains(removeFrom.next())) { + removeFrom.remove(); + modified = true; + } + } + return modified; + } + + /** + * Determines whether two iterators contain equal elements in the same order. + * More specifically, this method returns {@code true} if {@code iterator1} + * and {@code iterator2} contain the same number of elements and every element + * of {@code iterator1} is equal to the corresponding element of + * {@code iterator2}. + * + *

Note that this will modify the supplied iterators, since they will have + * been advanced some number of elements forward. + */ + public static boolean elementsEqual( + Iterator iterator1, Iterator iterator2) { + while (iterator1.hasNext()) { + if (!iterator2.hasNext()) { + return false; + } + Object o1 = iterator1.next(); + Object o2 = iterator2.next(); + if (!Objects.equal(o1, o2)) { + return false; + } + } + return !iterator2.hasNext(); + } + + /** + * Returns a string representation of {@code iterator}, with the format + * {@code [e1, e2, ..., en]}. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + */ + public static String toString(Iterator iterator) { + if (!iterator.hasNext()) { + return "[]"; + } + StringBuilder builder = new StringBuilder(); + builder.append('[').append(iterator.next()); + while (iterator.hasNext()) { + builder.append(", ").append(iterator.next()); + } + return builder.append(']').toString(); + } + + /** + * Returns the single element contained in {@code iterator}. + * + * @throws NoSuchElementException if the iterator is empty + * @throws IllegalArgumentException if the iterator contains multiple + * elements. The state of the iterator is unspecified. + */ + public static T getOnlyElement(Iterator iterator) { + T first = iterator.next(); + if (!iterator.hasNext()) { + return first; + } + + StringBuilder sb = new StringBuilder(); + sb.append("expected one element but was: <" + first); + for (int i = 0; i < 4 && iterator.hasNext(); i++) { + sb.append(", " + iterator.next()); + } + if (iterator.hasNext()) { + sb.append(", ..."); + } + sb.append(">"); + + throw new IllegalArgumentException(sb.toString()); + } + + /** + * Returns the single element contained in {@code iterator}, or {@code + * defaultValue} if the iterator is empty. + * + * @throws IllegalArgumentException if the iterator contains multiple + * elements. The state of the iterator is unspecified. + */ + public static T getOnlyElement( + Iterator iterator, @Nullable T defaultValue) { + return iterator.hasNext() ? getOnlyElement(iterator) : defaultValue; + } + + /** + * Copies an iterator's elements into an array. The iterator will be left + * exhausted: its {@code hasNext()} method will return {@code false}. + * + * @param iterator the iterator to copy + * @param type the type of the elements + * @return a newly-allocated array into which all the elements of the iterator + * have been copied + */ + @GwtIncompatible("Array.newArray") + public static T[] toArray( + Iterator iterator, Class type) { + List list = Lists.newArrayList(iterator); + return Iterables.toArray(list, type); + } + + /** + * Adds all elements in {@code iterator} to {@code collection}. The iterator + * will be left exhausted: its {@code hasNext()} method will return + * {@code false}. + * + * @return {@code true} if {@code collection} was modified as a result of this + * operation + */ + public static boolean addAll( + Collection addTo, Iterator iterator) { + checkNotNull(addTo); + boolean wasModified = false; + while (iterator.hasNext()) { + wasModified |= addTo.add(iterator.next()); + } + return wasModified; + } + + /** + * Returns the number of elements in the specified iterator that equal the + * specified object. The iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @see Collections#frequency + */ + public static int frequency(Iterator iterator, @Nullable Object element) { + int result = 0; + if (element == null) { + while (iterator.hasNext()) { + if (iterator.next() == null) { + result++; + } + } + } else { + while (iterator.hasNext()) { + if (element.equals(iterator.next())) { + result++; + } + } + } + return result; + } + + /** + * Returns an iterator that cycles indefinitely over the elements of {@code + * iterable}. + * + *

The returned iterator supports {@code remove()} if the provided iterator + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, which is no longer in {@code iterable}. The iterator's + * {@code hasNext()} method returns {@code true} until {@code iterable} is + * empty. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + */ + public static Iterator cycle(final Iterable iterable) { + checkNotNull(iterable); + return new Iterator() { + Iterator iterator = emptyIterator(); + Iterator removeFrom; + + public boolean hasNext() { + if (!iterator.hasNext()) { + iterator = iterable.iterator(); + } + return iterator.hasNext(); + } + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + removeFrom = iterator; + return iterator.next(); + } + public void remove() { + checkState(removeFrom != null, + "no calls to next() since last call to remove()"); + removeFrom.remove(); + removeFrom = null; + } + }; + } + + /** + * Returns an iterator that cycles indefinitely over the provided elements. + * + *

The returned iterator supports {@code remove()} if the provided iterator + * does. After {@code remove()} is called, subsequent cycles omit the removed + * element, but {@code elements} does not change. The iterator's + * {@code hasNext()} method returns {@code true} until all of the original + * elements have been removed. + * + *

Warning: Typical uses of the resulting iterator may produce an + * infinite loop. You should use an explicit {@code break} or be certain that + * you will eventually remove all the elements. + */ + public static Iterator cycle(T... elements) { + return cycle(Lists.newArrayList(elements)); + } + + /** + * Combines two iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}. The source iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterator concat(Iterator a, + Iterator b) { + checkNotNull(a); + checkNotNull(b); + return concat(Arrays.asList(a, b).iterator()); + } + + /** + * Combines three iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}, followed by the elements in {@code c}. The source iterators + * are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterator concat(Iterator a, + Iterator b, Iterator c) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + return concat(Arrays.asList(a, b, c).iterator()); + } + + /** + * Combines four iterators into a single iterator. The returned iterator + * iterates across the elements in {@code a}, followed by the elements in + * {@code b}, followed by the elements in {@code c}, followed by the elements + * in {@code d}. The source iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + */ + @SuppressWarnings("unchecked") + public static Iterator concat(Iterator a, + Iterator b, Iterator c, + Iterator d) { + checkNotNull(a); + checkNotNull(b); + checkNotNull(c); + checkNotNull(d); + return concat(Arrays.asList(a, b, c, d).iterator()); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator + * iterates across the elements of each iterator in {@code inputs}. The input + * iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. + * + * @throws NullPointerException if any of the provided iterators is null + */ + public static Iterator concat(Iterator... inputs) { + return concat(ImmutableList.of(inputs).iterator()); + } + + /** + * Combines multiple iterators into a single iterator. The returned iterator + * iterates across the elements of each iterator in {@code inputs}. The input + * iterators are not polled until necessary. + * + *

The returned iterator supports {@code remove()} when the corresponding + * input iterator supports it. The methods of the returned iterator may throw + * {@code NullPointerException} if any of the input iterators are null. + */ + public static Iterator concat( + final Iterator> inputs) { + checkNotNull(inputs); + return new Iterator() { + Iterator current = emptyIterator(); + Iterator removeFrom; + + public boolean hasNext() { + // http://code.google.com/p/google-collections/issues/detail?id=151 + // current.hasNext() might be relatively expensive, worth minimizing. + boolean currentHasNext; + while (!(currentHasNext = current.hasNext()) && inputs.hasNext()) { + current = inputs.next(); + } + return currentHasNext; + } + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + removeFrom = current; + return current.next(); + } + public void remove() { + checkState(removeFrom != null, + "no calls to next() since last call to remove()"); + removeFrom.remove(); + removeFrom = null; + } + }; + } + + /** + * Divides an iterator into unmodifiable sublists of the given size (the final + * list may be smaller). For example, partitioning an iterator containing + * {@code [a, b, c, d, e]} with a partition size of 3 yields {@code + * [[a, b, c], [d, e]]} -- an outer iterator containing two inner lists of + * three and two elements, all in the original order. + * + *

The returned lists implement {@link java.util.RandomAccess}. + * + * @param iterator the iterator to return a partitioned view of + * @param size the desired size of each partition (the last may be smaller) + * @return an iterator of immutable lists containing the elements of {@code + * iterator} divided into partitions + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static UnmodifiableIterator> partition( + Iterator iterator, int size) { + return partitionImpl(iterator, size, false); + } + + /** + * Divides an iterator into unmodifiable sublists of the given size, padding + * the final iterator with null values if necessary. For example, partitioning + * an iterator containing {@code [a, b, c, d, e]} with a partition size of 3 + * yields {@code [[a, b, c], [d, e, null]]} -- an outer iterator containing + * two inner lists of three elements each, all in the original order. + * + *

The returned lists implement {@link java.util.RandomAccess}. + * + * @param iterator the iterator to return a partitioned view of + * @param size the desired size of each partition + * @return an iterator of immutable lists containing the elements of {@code + * iterator} divided into partitions (the final iterable may have + * trailing null elements) + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + public static UnmodifiableIterator> paddedPartition( + Iterator iterator, int size) { + return partitionImpl(iterator, size, true); + } + + private static UnmodifiableIterator> partitionImpl( + final Iterator iterator, final int size, final boolean pad) { + checkNotNull(iterator); + checkArgument(size > 0); + return new UnmodifiableIterator>() { + public boolean hasNext() { + return iterator.hasNext(); + } + public List next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Object[] array = new Object[size]; + int count = 0; + for (; count < size && iterator.hasNext(); count++) { + array[count] = iterator.next(); + } + + @SuppressWarnings("unchecked") // we only put Ts in it + List list = Collections.unmodifiableList( + (List) Arrays.asList(array)); + return (pad || count == size) ? list : Platform.subList(list, 0, count); + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. + */ + public static UnmodifiableIterator filter( + final Iterator unfiltered, final Predicate predicate) { + checkNotNull(unfiltered); + checkNotNull(predicate); + return new AbstractIterator() { + @Override protected T computeNext() { + while (unfiltered.hasNext()) { + T element = unfiltered.next(); + if (predicate.apply(element)) { + return element; + } + } + return endOfData(); + } + }; + } + + /** + * Returns all instances of class {@code type} in {@code unfiltered}. The + * returned iterator has elements whose class is {@code type} or a subclass of + * {@code type}. + * + * @param unfiltered an iterator containing objects of any type + * @param type the type of elements desired + * @return an unmodifiable iterator containing all elements of the original + * iterator that were of the requested type + */ + @SuppressWarnings("unchecked") // can cast to because non-Ts are removed + @GwtIncompatible("Class.isInstance") + public static UnmodifiableIterator filter( + Iterator unfiltered, Class type) { + return (UnmodifiableIterator) + filter(unfiltered, Predicates.instanceOf(type)); + } + + /** + * Returns {@code true} if one or more elements returned by {@code iterator} + * satisfy the given predicate. + */ + public static boolean any( + Iterator iterator, Predicate predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (predicate.apply(element)) { + return true; + } + } + return false; + } + + /** + * Returns {@code true} if every element returned by {@code iterator} + * satisfies the given predicate. If {@code iterator} is empty, {@code true} + * is returned. + */ + public static boolean all( + Iterator iterator, Predicate predicate) { + checkNotNull(predicate); + while (iterator.hasNext()) { + T element = iterator.next(); + if (!predicate.apply(element)) { + return false; + } + } + return true; + } + + /** + * Returns the first element in {@code iterator} that satisfies the given + * predicate. If a matching element is found, the iterator will be left in a + * state such that calling {@code iterator.remove()} will remove the found + * item. If no such element is found, the iterator will be left exhausted: its + * {@code hasNext()} method will return {@code false}. + * + * @return the first matching element in {@code iterator} + * @throws NoSuchElementException if no element in {@code iterator} matches + * the given predicate + */ + public static T find(Iterator iterator, Predicate predicate) + { + return filter(iterator, predicate).next(); + } + + /** + * Returns an iterator that applies {@code function} to each element of {@code + * fromIterator}. + * + *

The returned iterator supports {@code remove()} if the provided iterator + * does. After a successful {@code remove()} call, {@code fromIterator} no + * longer contains the corresponding element. + */ + public static Iterator transform(final Iterator fromIterator, + final Function function) { + checkNotNull(fromIterator); + checkNotNull(function); + return new Iterator() { + public boolean hasNext() { + return fromIterator.hasNext(); + } + public T next() { + F from = fromIterator.next(); + return function.apply(from); + } + public void remove() { + fromIterator.remove(); + } + }; + } + + /** + * Advances {@code iterator} {@code position + 1} times, returning the element + * at the {@code position}th position. + * + * @param position position of the element to return + * @return the element at the specified position in {@code iterator} + * @throws IndexOutOfBoundsException if {@code position} is negative or + * greater than or equal to the number of elements remaining in + * {@code iterator} + */ + public static T get(Iterator iterator, int position) { + if (position < 0) { + throw new IndexOutOfBoundsException("position (" + position + + ") must not be negative"); + } + + int skipped = 0; + while (iterator.hasNext()) { + T t = iterator.next(); + if (skipped++ == position) { + return t; + } + } + + throw new IndexOutOfBoundsException("position (" + position + + ") must be less than the number of elements that remained (" + + skipped + ")"); + } + + /** + * Advances {@code iterator} to the end, returning the last element. + * + * @return the last element of {@code iterator} + * @throws NoSuchElementException if the iterator has no remaining elements + */ + public static T getLast(Iterator iterator) { + while (true) { + T current = iterator.next(); + if (!iterator.hasNext()) { + return current; + } + } + } + + // Methods only in Iterators, not in Iterables + + /** + * Returns an iterator containing the elements of {@code array} in order. The + * returned iterator is a view of the array; subsequent changes to the array + * will be reflected in the iterator. + * + *

Note: It is often preferable to represent your data using a + * collection type, for example using {@link Arrays#asList(Object[])}, making + * this method unnecessary. + * + *

The {@code Iterable} equivalent of this method is either {@link + * Arrays#asList(Object[])} or {@link ImmutableList#of(Object[])}}. + */ + public static UnmodifiableIterator forArray(final T... array) { + // optimized. benchmarks at nearly 2x of the straightforward impl + return new UnmodifiableIterator() { + final int length = array.length; + int i = 0; + public boolean hasNext() { + return i < length; + } + public T next() { + try { + // 'return array[i++];' almost works + T t = array[i]; + i++; + return t; + } catch (ArrayIndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } + } + }; + } + + /** + * Returns an iterator containing the elements in the specified range of + * {@code array} in order. The returned iterator is a view of the array; + * subsequent changes to the array will be reflected in the iterator. + * + *

The {@code Iterable} equivalent of this method is {@code + * Arrays.asList(array).subList(offset, offset + length)}. + * + * @param array array to read elements out of + * @param offset index of first array element to retrieve + * @param length number of elements in iteration + * + * @throws IndexOutOfBoundsException if {@code offset} is negative, + * {@code length} is negative, or {@code offset + length > array.length} + */ + static UnmodifiableIterator forArray( + final T[] array, final int offset, int length) { + checkArgument(length >= 0); + final int end = offset + length; + + // Technically we should give a slightly more descriptive error on overflow + Preconditions.checkPositionIndexes(offset, end, array.length); + + // If length == 0 is a common enough case, we could return emptyIterator(). + + return new UnmodifiableIterator() { + int i = offset; + public boolean hasNext() { + return i < end; + } + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return array[i++]; + } + }; + } + + /** + * Returns an iterator containing only {@code value}. + * + *

The {@link Iterable} equivalent of this method is {@link + * Collections#singleton}. + */ + public static UnmodifiableIterator singletonIterator( + @Nullable final T value) { + return new UnmodifiableIterator() { + boolean done; + public boolean hasNext() { + return !done; + } + public T next() { + if (done) { + throw new NoSuchElementException(); + } + done = true; + return value; + } + }; + } + + /** + * Adapts an {@code Enumeration} to the {@code Iterator} interface. + * + *

This method has no equivalent in {@link Iterables} because viewing an + * {@code Enumeration} as an {@code Iterable} is impossible. However, the + * contents can be copied into a collection using {@link + * Collections#list}. + */ + public static UnmodifiableIterator forEnumeration( + final Enumeration enumeration) { + checkNotNull(enumeration); + return new UnmodifiableIterator() { + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + public T next() { + return enumeration.nextElement(); + } + }; + } + + /** + * Adapts an {@code Iterator} to the {@code Enumeration} interface. + * + *

The {@code Iterable} equivalent of this method is either {@link + * Collections#enumeration} (if you have a {@link Collection}), or + * {@code Iterators.asEnumeration(collection.iterator())}. + */ + public static Enumeration asEnumeration(final Iterator iterator) { + checkNotNull(iterator); + return new Enumeration() { + public boolean hasMoreElements() { + return iterator.hasNext(); + } + public T nextElement() { + return iterator.next(); + } + }; + } + + /** + * Implementation of PeekingIterator that avoids peeking unless necessary. + */ + private static class PeekingImpl implements PeekingIterator { + + private final Iterator iterator; + private boolean hasPeeked; + private E peekedElement; + + public PeekingImpl(Iterator iterator) { + this.iterator = checkNotNull(iterator); + } + + public boolean hasNext() { + return hasPeeked || iterator.hasNext(); + } + + public E next() { + if (!hasPeeked) { + return iterator.next(); + } + E result = peekedElement; + hasPeeked = false; + peekedElement = null; + return result; + } + + public void remove() { + checkState(!hasPeeked, "Can't remove after you've peeked at next"); + iterator.remove(); + } + + public E peek() { + if (!hasPeeked) { + peekedElement = iterator.next(); + hasPeeked = true; + } + return peekedElement; + } + } + + /** + * Returns a {@code PeekingIterator} backed by the given iterator. + * + *

Calls to the {@code peek} method with no intervening calls to {@code + * next} do not affect the iteration, and hence return the same object each + * time. A subsequent call to {@code next} is guaranteed to return the same + * object again. For example:

   {@code
+   *
+   *   PeekingIterator peekingIterator =
+   *       Iterators.peekingIterator(Iterators.forArray("a", "b"));
+   *   String a1 = peekingIterator.peek(); // returns "a"
+   *   String a2 = peekingIterator.peek(); // also returns "a"
+   *   String a3 = peekingIterator.next(); // also returns "a"}
+ * + * Any structural changes to the underlying iteration (aside from those + * performed by the iterator's own {@link PeekingIterator#remove()} method) + * will leave the iterator in an undefined state. + * + *

The returned iterator does not support removal after peeking, as + * explained by {@link PeekingIterator#remove()}. + * + *

Note: If the given iterator is already a {@code PeekingIterator}, + * it might be returned to the caller, although this is neither + * guaranteed to occur nor required to be consistent. For example, this + * method might choose to pass through recognized implementations of + * {@code PeekingIterator} when the behavior of the implementation is + * known to meet the contract guaranteed by this method. + * + *

There is no {@link Iterable} equivalent to this method, so use this + * method to wrap each individual iterator as it is generated. + * + * @param iterator the backing iterator. The {@link PeekingIterator} assumes + * ownership of this iterator, so users should cease making direct calls + * to it after calling this method. + * @return a peeking iterator backed by that iterator. Apart from the + * additional {@link PeekingIterator#peek()} method, this iterator behaves + * exactly the same as {@code iterator}. + */ + public static PeekingIterator peekingIterator( + Iterator iterator) { + if (iterator instanceof PeekingImpl) { + // Safe to cast to because PeekingImpl only uses T + // covariantly (and cannot be subclassed to add non-covariant uses). + @SuppressWarnings("unchecked") + PeekingImpl peeking = (PeekingImpl) iterator; + return peeking; + } + return new PeekingImpl(iterator); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedHashMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedHashMultimap.java new file mode 100644 index 00000000000..76e360c2b6b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedHashMultimap.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import org.elasticsearch.util.gcommon.base.Preconditions; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of {@code Multimap} that does not allow duplicate key-value + * entries and that returns collections whose iterators follow the ordering in + * which the data was added to the multimap. + * + *

The collections returned by {@code keySet}, {@code keys}, and {@code + * asMap} iterate through the keys in the order they were first added to the + * multimap. Similarly, {@code get}, {@code removeAll}, and {@code + * replaceValues} return collections that iterate through the values in the + * order they were added. The collections generated by {@code entries} and + * {@code values} iterate across the key-value mappings in the order they were + * added to the multimap. + * + *

The iteration ordering of the collections generated by {@code keySet}, + * {@code keys}, and {@code asMap} has a few subtleties. As long as the set of + * keys remains unchanged, adding or removing mappings does not affect the key + * iteration order. However, if you remove all values associated with a key and + * then add the key back to the multimap, that key will come last in the key + * iteration order. + * + *

The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSetMultimap}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public final class LinkedHashMultimap extends AbstractSetMultimap { + private static final int DEFAULT_VALUES_PER_KEY = 8; + + @VisibleForTesting + transient int expectedValuesPerKey = DEFAULT_VALUES_PER_KEY; + + /** + * Map entries with an iteration order corresponding to the order in which the + * key-value pairs were added to the multimap. + */ + // package-private for GWT deserialization + transient Collection> linkedEntries; + + /** + * Creates a new, empty {@code LinkedHashMultimap} with the default initial + * capacities. + */ + public static LinkedHashMultimap create() { + return new LinkedHashMultimap(); + } + + /** + * Constructs an empty {@code LinkedHashMultimap} with enough capacity to hold + * the specified numbers of keys and values without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @param expectedValuesPerKey the expected average number of values per key + * @throws IllegalArgumentException if {@code expectedKeys} or {@code + * expectedValuesPerKey} is negative + */ + public static LinkedHashMultimap create( + int expectedKeys, int expectedValuesPerKey) { + return new LinkedHashMultimap(expectedKeys, expectedValuesPerKey); + } + + /** + * Constructs a {@code LinkedHashMultimap} with the same mappings as the + * specified multimap. If a key-value mapping appears multiple times in the + * input multimap, it only appears once in the constructed multimap. The new + * multimap has the same {@link Multimap#entries()} iteration order as the + * input multimap, except for excluding duplicate mappings. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static LinkedHashMultimap create( + Multimap multimap) { + return new LinkedHashMultimap(multimap); + } + + private LinkedHashMultimap() { + super(new LinkedHashMap>()); + linkedEntries = Sets.newLinkedHashSet(); + } + + private LinkedHashMultimap(int expectedKeys, int expectedValuesPerKey) { + super(new LinkedHashMap>(expectedKeys)); + Preconditions.checkArgument(expectedValuesPerKey >= 0); + this.expectedValuesPerKey = expectedValuesPerKey; + linkedEntries = new LinkedHashSet>( + expectedKeys * expectedValuesPerKey); + } + + private LinkedHashMultimap(Multimap multimap) { + super(new LinkedHashMap>( + Maps.capacity(multimap.keySet().size()))); + linkedEntries + = new LinkedHashSet>(Maps.capacity(multimap.size())); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code LinkedHashSet} for a collection of values for + * one key. + * + * @return a new {@code LinkedHashSet} containing a collection of values for + * one key + */ + @Override Set createCollection() { + return new LinkedHashSet(Maps.capacity(expectedValuesPerKey)); + } + + /** + * {@inheritDoc} + * + *

Creates a decorated {@code LinkedHashSet} that also keeps track of the + * order in which key-value pairs are added to the multimap. + * + * @param key key to associate with values in the collection + * @return a new decorated {@code LinkedHashSet} containing a collection of + * values for one key + */ + @Override Collection createCollection(@Nullable K key) { + return new SetDecorator(key, createCollection()); + } + + private class SetDecorator extends ForwardingSet { + final Set delegate; + final K key; + + SetDecorator(@Nullable K key, Set delegate) { + this.delegate = delegate; + this.key = key; + } + + @Override protected Set delegate() { + return delegate; + } + + Map.Entry createEntry(@Nullable E value) { + return Maps.immutableEntry(key, value); + } + + Collection> createEntries(Collection values) { + // converts a collection of values into a list of key/value map entries + Collection> entries + = Lists.newArrayListWithExpectedSize(values.size()); + for (E value : values) { + entries.add(createEntry(value)); + } + return entries; + } + + @Override public boolean add(@Nullable V value) { + boolean changed = delegate.add(value); + if (changed) { + linkedEntries.add(createEntry(value)); + } + return changed; + } + + @Override public boolean addAll(Collection values) { + boolean changed = delegate.addAll(values); + if (changed) { + linkedEntries.addAll(createEntries(delegate())); + } + return changed; + } + + @Override public void clear() { + linkedEntries.removeAll(createEntries(delegate())); + delegate.clear(); + } + + @Override public Iterator iterator() { + final Iterator delegateIterator = delegate.iterator(); + return new Iterator() { + V value; + + public boolean hasNext() { + return delegateIterator.hasNext(); + } + public V next() { + value = delegateIterator.next(); + return value; + } + public void remove() { + delegateIterator.remove(); + linkedEntries.remove(createEntry(value)); + } + }; + } + + @Override public boolean remove(@Nullable Object value) { + boolean changed = delegate.remove(value); + if (changed) { + /* + * linkedEntries.remove() will return false when this method is called + * by entries().iterator().remove() + */ + linkedEntries.remove(createEntry(value)); + } + return changed; + } + + @Override public boolean removeAll(Collection values) { + boolean changed = delegate.removeAll(values); + if (changed) { + linkedEntries.removeAll(createEntries(values)); + } + return changed; + } + + @Override public boolean retainAll(Collection values) { + /* + * Calling linkedEntries.retainAll() would incorrectly remove values + * with other keys. + */ + boolean changed = false; + Iterator iterator = delegate.iterator(); + while (iterator.hasNext()) { + V value = iterator.next(); + if (!values.contains(value)) { + iterator.remove(); + linkedEntries.remove(Maps.immutableEntry(key, value)); + changed = true; + } + } + return changed; + } + } + + /** + * {@inheritDoc} + * + *

Generates an iterator across map entries that follows the ordering in + * which the key-value pairs were added to the multimap. + * + * @return a key-value iterator with the correct ordering + */ + @Override Iterator> createEntryIterator() { + final Iterator> delegateIterator = linkedEntries.iterator(); + + return new Iterator>() { + Map.Entry entry; + + public boolean hasNext() { + return delegateIterator.hasNext(); + } + + public Map.Entry next() { + entry = delegateIterator.next(); + return entry; + } + + public void remove() { + // Remove from iterator first to keep iterator valid. + delegateIterator.remove(); + LinkedHashMultimap.this.remove(entry.getKey(), entry.getValue()); + } + }; + } + + /** + * {@inheritDoc} + * + *

If {@code values} is not empty and the multimap already contains a + * mapping for {@code key}, the {@code keySet()} ordering is unchanged. + * However, the provided values always come last in the {@link #entries()} and + * {@link #values()} iteration orderings. + */ + @Override public Set replaceValues( + @Nullable K key, Iterable values) { + return super.replaceValues(key, values); + } + + /** + * Returns a set of all key-value pairs. Changes to the returned set will + * update the underlying multimap, and vice versa. The entries set does not + * support the {@code add} or {@code addAll} operations. + * + *

The iterator generated by the returned set traverses the entries in the + * order they were added to the multimap. + * + *

Each entry is an immutable snapshot of a key-value mapping in the + * multimap, taken at the time the entry is returned by a method call to the + * collection or its iterator. + */ + @Override public Set> entries() { + return super.entries(); + } + + /** + * Returns a collection of all values in the multimap. Changes to the returned + * collection will update the underlying multimap, and vice versa. + * + *

The iterator generated by the returned collection traverses the values + * in the order they were added to the multimap. + */ + @Override public Collection values() { + return super.values(); + } + + // Unfortunately, the entries() ordering does not determine the key ordering; + // see the example in the LinkedListMultimap class Javadoc. + + /** + * @serialData the number of distinct keys, and then for each distinct key: + * the first key, the number of values for that key, and the key's values, + * followed by successive keys and values from the entries() ordering + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(expectedValuesPerKey); + Serialization.writeMultimap(this, stream); + for (Map.Entry entry : linkedEntries) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + expectedValuesPerKey = stream.readInt(); + int distinctKeys = Serialization.readCount(stream); + setMap(new LinkedHashMap>(Maps.capacity(distinctKeys))); + linkedEntries = new LinkedHashSet>( + distinctKeys * expectedValuesPerKey); + Serialization.populateMultimap(this, stream, distinctKeys); + linkedEntries.clear(); // will clear and repopulate entries + for (int i = 0; i < size(); i++) { + @SuppressWarnings("unchecked") // reading data stored by writeObject + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + V value = (V) stream.readObject(); + linkedEntries.add(Maps.immutableEntry(key, value)); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedHashMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedHashMultiset.java new file mode 100644 index 00000000000..2c6c61b4586 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedHashMultiset.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.LinkedHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@code Multiset} implementation with predictable iteration order. Its + * iterator orders elements according to when the first occurrence of the + * element was added. When the multiset contains multiple instances of an + * element, those instances are consecutive in the iteration order. If all + * occurrences of an element are removed, after which that element is added to + * the multiset, the element will appear at the end of the iteration. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // we're overriding default serialization +public final class LinkedHashMultiset extends AbstractMapBasedMultiset { + + /** + * Creates a new, empty {@code LinkedHashMultiset} using the default initial + * capacity. + */ + public static LinkedHashMultiset create() { + return new LinkedHashMultiset(); + } + + /** + * Creates a new, empty {@code LinkedHashMultiset} with the specified expected + * number of distinct elements. + * + * @param distinctElements the expected number of distinct elements + * @throws IllegalArgumentException if {@code distinctElements} is negative + */ + public static LinkedHashMultiset create(int distinctElements) { + return new LinkedHashMultiset(distinctElements); + } + + /** + * Creates a new {@code LinkedHashMultiset} containing the specified elements. + * + * @param elements the elements that the multiset should contain + */ + public static LinkedHashMultiset create( + Iterable elements) { + LinkedHashMultiset multiset = + create(Multisets.inferDistinctElements(elements)); + Iterables.addAll(multiset, elements); + return multiset; + } + + private LinkedHashMultiset() { + super(new LinkedHashMap()); + } + + private LinkedHashMultiset(int distinctElements) { + // Could use newLinkedHashMapWithExpectedSize() if it existed + super(new LinkedHashMap(Maps.capacity(distinctElements))); + } + + /** + * @serialData the number of distinct elements, the first element, its count, + * the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + int distinctElements = Serialization.readCount(stream); + setBackingMap(new LinkedHashMap( + Maps.capacity(distinctElements))); + Serialization.populateMultiset(this, stream, distinctElements); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedListMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedListMultimap.java new file mode 100644 index 00000000000..c284e97faa7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/LinkedListMultimap.java @@ -0,0 +1,951 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Objects; +import org.elasticsearch.util.gcommon.base.Preconditions; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; +import static org.elasticsearch.util.gcommon.collect.Multisets.setCountImpl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSequentialList; +import java.util.AbstractSet; +import java.util.Collection; +import static java.util.Collections.unmodifiableList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An implementation of {@code ListMultimap} that supports deterministic + * iteration order for both keys and values. The iteration order is preserved + * across non-distinct key values. For example, for the following multimap + * definition:

   {@code
+ *
+ *   Multimap multimap = LinkedListMultimap.create();
+ *   multimap.put(key1, foo);
+ *   multimap.put(key2, bar);
+ *   multimap.put(key1, baz);}
+ * + * ... the iteration order for {@link #keys()} is {@code [key1, key2, key1]}, + * and similarly for {@link #entries()}. Unlike {@link LinkedHashMultimap}, the + * iteration order is kept consistent between keys, entries and values. For + * example, calling:
   {@code
+ *
+ *   map.remove(key1, foo);}
+ * + * changes the entries iteration order to {@code [key2=bar, key1=baz]} and the + * key iteration order to {@code [key2, key1]}. The {@link #entries()} iterator + * returns mutable map entries, and {@link #replaceValues} attempts to preserve + * iteration order as much as possible. + * + *

The collections returned by {@link #keySet} and {@link #asMap} iterate + * through the keys in the order they were first added to the multimap. + * Similarly, {@link #get}, {@link #removeAll}, and {@link #replaceValues} + * return collections that iterate through the values in the order they were + * added. The collections generated by {@link #entries}, {@link #keys}, and + * {@link #values} iterate across the key-value mappings in the order they were + * added to the multimap. + * + *

Keys and values may be null. All optional multimap methods are supported, + * and all returned views are modifiable. + * + *

The methods {@link #get}, {@link #keySet}, {@link #keys}, {@link #values}, + * {@link #entries}, and {@link #asMap} return collections that are views of the + * multimap. If the multimap is modified while an iteration over any of those + * collections is in progress, except through the iterator's own {@code remove} + * operation, the results of the iteration are undefined. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedListMultimap}. + * + * @author Mike Bostock + */ +@GwtCompatible(serializable = true) +public final class LinkedListMultimap + implements ListMultimap, Serializable { + /* + * Order is maintained using a linked list containing all key-value pairs. In + * addition, a series of disjoint linked lists of "siblings", each containing + * the values for a specific key, is used to implement {@link + * ValueForKeyIterator} in constant time. + */ + + private static final class Node { + final K key; + V value; + Node next; // the next node (with any key) + Node previous; // the previous node (with any key) + Node nextSibling; // the next node with the same key + Node previousSibling; // the previous node with the same key + + Node(@Nullable K key, @Nullable V value) { + this.key = key; + this.value = value; + } + + @Override public String toString() { + return key + "=" + value; + } + } + + private transient Node head; // the head for all keys + private transient Node tail; // the tail for all keys + private transient Multiset keyCount; // the number of values for each key + private transient Map> keyToKeyHead; // the head for a given key + private transient Map> keyToKeyTail; // the tail for a given key + + /** + * Creates a new, empty {@code LinkedListMultimap} with the default initial + * capacity. + */ + public static LinkedListMultimap create() { + return new LinkedListMultimap(); + } + + /** + * Constructs an empty {@code LinkedListMultimap} with enough capacity to hold + * the specified number of keys without rehashing. + * + * @param expectedKeys the expected number of distinct keys + * @throws IllegalArgumentException if {@code expectedKeys} is negative + */ + public static LinkedListMultimap create(int expectedKeys) { + return new LinkedListMultimap(expectedKeys); + } + + /** + * Constructs a {@code LinkedListMultimap} with the same mappings as the + * specified {@code Multimap}. The new multimap has the same + * {@link Multimap#entries()} iteration order as the input multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + public static LinkedListMultimap create( + Multimap multimap) { + return new LinkedListMultimap(multimap); + } + + private LinkedListMultimap() { + keyCount = LinkedHashMultiset.create(); + keyToKeyHead = Maps.newHashMap(); + keyToKeyTail = Maps.newHashMap(); + } + + private LinkedListMultimap(int expectedKeys) { + keyCount = LinkedHashMultiset.create(expectedKeys); + keyToKeyHead = Maps.newHashMapWithExpectedSize(expectedKeys); + keyToKeyTail = Maps.newHashMapWithExpectedSize(expectedKeys); + } + + private LinkedListMultimap(Multimap multimap) { + this(multimap.keySet().size()); + putAll(multimap); + } + + /** + * Adds a new node for the specified key-value pair before the specified + * {@code nextSibling} element, or at the end of the list if {@code + * nextSibling} is null. Note: if {@code nextSibling} is specified, it MUST be + * for an node for the same {@code key}! + */ + private Node addNode( + @Nullable K key, @Nullable V value, @Nullable Node nextSibling) { + Node node = new Node(key, value); + if (head == null) { // empty list + head = tail = node; + keyToKeyHead.put(key, node); + keyToKeyTail.put(key, node); + } else if (nextSibling == null) { // non-empty list, add to tail + tail.next = node; + node.previous = tail; + Node keyTail = keyToKeyTail.get(key); + if (keyTail == null) { // first for this key + keyToKeyHead.put(key, node); + } else { + keyTail.nextSibling = node; + node.previousSibling = keyTail; + } + keyToKeyTail.put(key, node); + tail = node; + } else { // non-empty list, insert before nextSibling + node.previous = nextSibling.previous; + node.previousSibling = nextSibling.previousSibling; + node.next = nextSibling; + node.nextSibling = nextSibling; + if (nextSibling.previousSibling == null) { // nextSibling was key head + keyToKeyHead.put(key, node); + } else { + nextSibling.previousSibling.nextSibling = node; + } + if (nextSibling.previous == null) { // nextSibling was head + head = node; + } else { + nextSibling.previous.next = node; + } + nextSibling.previous = node; + nextSibling.previousSibling = node; + } + keyCount.add(key); + return node; + } + + /** + * Removes the specified node from the linked list. This method is only + * intended to be used from the {@code Iterator} classes. See also {@link + * LinkedListMultimap#removeAllNodes(Object)}. + */ + private void removeNode(Node node) { + if (node.previous != null) { + node.previous.next = node.next; + } else { // node was head + head = node.next; + } + if (node.next != null) { + node.next.previous = node.previous; + } else { // node was tail + tail = node.previous; + } + if (node.previousSibling != null) { + node.previousSibling.nextSibling = node.nextSibling; + } else if (node.nextSibling != null) { // node was key head + keyToKeyHead.put(node.key, node.nextSibling); + } else { + keyToKeyHead.remove(node.key); // don't leak a key-null entry + } + if (node.nextSibling != null) { + node.nextSibling.previousSibling = node.previousSibling; + } else if (node.previousSibling != null) { // node was key tail + keyToKeyTail.put(node.key, node.previousSibling); + } else { + keyToKeyTail.remove(node.key); // don't leak a key-null entry + } + keyCount.remove(node.key); + } + + /** Removes all nodes for the specified key. */ + private void removeAllNodes(@Nullable Object key) { + for (Iterator i = new ValueForKeyIterator(key); i.hasNext();) { + i.next(); + i.remove(); + } + } + + /** Helper method for verifying that an iterator element is present. */ + private static void checkElement(@Nullable Object node) { + if (node == null) { + throw new NoSuchElementException(); + } + } + + /** An {@code Iterator} over all nodes. */ + private class NodeIterator implements Iterator> { + Node next = head; + Node current; + + public boolean hasNext() { + return next != null; + } + public Node next() { + checkElement(next); + current = next; + next = next.next; + return current; + } + public void remove() { + checkState(current != null); + removeNode(current); + current = null; + } + } + + /** An {@code Iterator} over distinct keys in key head order. */ + private class DistinctKeyIterator implements Iterator { + final Set seenKeys = new HashSet(Maps.capacity(keySet().size())); + Node next = head; + Node current; + + public boolean hasNext() { + return next != null; + } + public K next() { + checkElement(next); + current = next; + seenKeys.add(current.key); + do { // skip ahead to next unseen key + next = next.next; + } while ((next != null) && !seenKeys.add(next.key)); + return current.key; + } + public void remove() { + checkState(current != null); + removeAllNodes(current.key); + current = null; + } + } + + /** A {@code ListIterator} over values for a specified key. */ + private class ValueForKeyIterator implements ListIterator { + final Object key; + int nextIndex; + Node next; + Node current; + Node previous; + + /** Constructs a new iterator over all values for the specified key. */ + ValueForKeyIterator(@Nullable Object key) { + this.key = key; + next = keyToKeyHead.get(key); + } + + /** + * Constructs a new iterator over all values for the specified key starting + * at the specified index. This constructor is optimized so that it starts + * at either the head or the tail, depending on which is closer to the + * specified index. This allows adds to the tail to be done in constant + * time. + * + * @throws IndexOutOfBoundsException if index is invalid + */ + public ValueForKeyIterator(@Nullable Object key, int index) { + int size = keyCount.count(key); + Preconditions.checkPositionIndex(index, size); + if (index >= (size / 2)) { + previous = keyToKeyTail.get(key); + nextIndex = size; + while (index++ < size) { + previous(); + } + } else { + next = keyToKeyHead.get(key); + while (index-- > 0) { + next(); + } + } + this.key = key; + current = null; + } + + public boolean hasNext() { + return next != null; + } + + public V next() { + checkElement(next); + previous = current = next; + next = next.nextSibling; + nextIndex++; + return current.value; + } + + public boolean hasPrevious() { + return previous != null; + } + + public V previous() { + checkElement(previous); + next = current = previous; + previous = previous.previousSibling; + nextIndex--; + return current.value; + } + + public int nextIndex() { + return nextIndex; + } + + public int previousIndex() { + return nextIndex - 1; + } + + public void remove() { + checkState(current != null); + if (current != next) { // removing next element + previous = current.previousSibling; + nextIndex--; + } else { + next = current.nextSibling; + } + removeNode(current); + current = null; + } + + public void set(V value) { + checkState(current != null); + current.value = value; + } + + @SuppressWarnings("unchecked") + public void add(V value) { + previous = addNode((K) key, value, next); + nextIndex++; + current = null; + } + } + + // Query Operations + + public int size() { + return keyCount.size(); + } + + public boolean isEmpty() { + return head == null; + } + + public boolean containsKey(@Nullable Object key) { + return keyToKeyHead.containsKey(key); + } + + public boolean containsValue(@Nullable Object value) { + for (Iterator> i = new NodeIterator(); i.hasNext();) { + if (Objects.equal(i.next().value, value)) { + return true; + } + } + return false; + } + + public boolean containsEntry(@Nullable Object key, @Nullable Object value) { + for (Iterator i = new ValueForKeyIterator(key); i.hasNext();) { + if (Objects.equal(i.next(), value)) { + return true; + } + } + return false; + } + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} always + */ + public boolean put(@Nullable K key, @Nullable V value) { + addNode(key, value, null); + return true; + } + + public boolean remove(@Nullable Object key, @Nullable Object value) { + Iterator values = new ValueForKeyIterator(key); + while (values.hasNext()) { + if (Objects.equal(values.next(), value)) { + values.remove(); + return true; + } + } + return false; + } + + // Bulk Operations + + public boolean putAll(@Nullable K key, Iterable values) { + boolean changed = false; + for (V value : values) { + changed |= put(key, value); + } + return changed; + } + + public boolean putAll(Multimap multimap) { + boolean changed = false; + for (Entry entry : multimap.entries()) { + changed |= put(entry.getKey(), entry.getValue()); + } + return changed; + } + + /** + * {@inheritDoc} + * + *

If any entries for the specified {@code key} already exist in the + * multimap, their values are changed in-place without affecting the iteration + * order. + * + *

The returned list is immutable and implements + * {@link java.util.RandomAccess}. + */ + public List replaceValues(@Nullable K key, Iterable values) { + List oldValues = getCopy(key); + ListIterator keyValues = new ValueForKeyIterator(key); + Iterator newValues = values.iterator(); + + // Replace existing values, if any. + while (keyValues.hasNext() && newValues.hasNext()) { + keyValues.next(); + keyValues.set(newValues.next()); + } + + // Remove remaining old values, if any. + while (keyValues.hasNext()) { + keyValues.next(); + keyValues.remove(); + } + + // Add remaining new values, if any. + while (newValues.hasNext()) { + keyValues.add(newValues.next()); + } + + return oldValues; + } + + private List getCopy(@Nullable Object key) { + return unmodifiableList(Lists.newArrayList(new ValueForKeyIterator(key))); + } + + /** + * {@inheritDoc} + * + *

The returned list is immutable and implements + * {@link java.util.RandomAccess}. + */ + public List removeAll(@Nullable Object key) { + List oldValues = getCopy(key); + removeAllNodes(key); + return oldValues; + } + + public void clear() { + head = null; + tail = null; + keyCount.clear(); + keyToKeyHead.clear(); + keyToKeyTail.clear(); + } + + // Views + + /** + * {@inheritDoc} + * + *

If the multimap is modified while an iteration over the list is in + * progress (except through the iterator's own {@code add}, {@code set} or + * {@code remove} operations) the results of the iteration are undefined. + * + *

The returned list is not serializable and does not have random access. + */ + public List get(final @Nullable K key) { + return new AbstractSequentialList() { + @Override public int size() { + return keyCount.count(key); + } + @Override public ListIterator listIterator(int index) { + return new ValueForKeyIterator(key, index); + } + @Override public boolean removeAll(Collection c) { + return Iterators.removeAll(iterator(), c); + } + @Override public boolean retainAll(Collection c) { + return Iterators.retainAll(iterator(), c); + } + }; + } + + private transient Set keySet; + + public Set keySet() { + Set result = keySet; + if (result == null) { + keySet = result = new AbstractSet() { + @Override public int size() { + return keyCount.elementSet().size(); + } + @Override public Iterator iterator() { + return new DistinctKeyIterator(); + } + @Override public boolean contains(Object key) { // for performance + return keyCount.contains(key); + } + }; + } + return result; + } + + private transient Multiset keys; + + public Multiset keys() { + Multiset result = keys; + if (result == null) { + keys = result = new MultisetView(); + } + return result; + } + + private class MultisetView extends AbstractCollection + implements Multiset { + + @Override public int size() { + return keyCount.size(); + } + + @Override public Iterator iterator() { + final Iterator> nodes = new NodeIterator(); + return new Iterator() { + public boolean hasNext() { + return nodes.hasNext(); + } + public K next() { + return nodes.next().key; + } + public void remove() { + nodes.remove(); + } + }; + } + + public int count(@Nullable Object key) { + return keyCount.count(key); + } + + public int add(@Nullable K key, int occurrences) { + throw new UnsupportedOperationException(); + } + + public int remove(@Nullable Object key, int occurrences) { + checkArgument(occurrences >= 0); + int oldCount = count(key); + Iterator values = new ValueForKeyIterator(key); + while ((occurrences-- > 0) && values.hasNext()) { + values.next(); + values.remove(); + } + return oldCount; + } + + public int setCount(K element, int count) { + return setCountImpl(this, element, count); + } + + public boolean setCount(K element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + @Override public boolean removeAll(Collection c) { + return Iterators.removeAll(iterator(), c); + } + + @Override public boolean retainAll(Collection c) { + return Iterators.retainAll(iterator(), c); + } + + public Set elementSet() { + return keySet(); + } + + public Set> entrySet() { + // TODO: lazy init? + return new AbstractSet>() { + @Override public int size() { + return keyCount.elementSet().size(); + } + + @Override public Iterator> iterator() { + final Iterator keyIterator = new DistinctKeyIterator(); + return new Iterator>() { + public boolean hasNext() { + return keyIterator.hasNext(); + } + public Entry next() { + final K key = keyIterator.next(); + return new Multisets.AbstractEntry() { + public K getElement() { + return key; + } + public int getCount() { + return keyCount.count(key); + } + }; + } + public void remove() { + keyIterator.remove(); + } + }; + } + }; + } + + @Override public boolean equals(@Nullable Object object) { + return keyCount.equals(object); + } + + @Override public int hashCode() { + return keyCount.hashCode(); + } + + @Override public String toString() { + return keyCount.toString(); // XXX observe order? + } + } + + private transient Collection valuesCollection; + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the values + * in the order they were added to the multimap. + */ + public Collection values() { + Collection result = valuesCollection; + if (result == null) { + valuesCollection = result = new AbstractCollection() { + @Override public int size() { + return keyCount.size(); + } + @Override public Iterator iterator() { + final Iterator> nodes = new NodeIterator(); + return new Iterator() { + public boolean hasNext() { + return nodes.hasNext(); + } + public V next() { + return nodes.next().value; + } + public void remove() { + nodes.remove(); + } + }; + } + }; + } + return result; + } + + private transient Collection> entries; + + /** + * {@inheritDoc} + * + *

The iterator generated by the returned collection traverses the entries + * in the order they were added to the multimap. + * + *

An entry's {@link Entry#getKey} method always returns the same key, + * regardless of what happens subsequently. As long as the corresponding + * key-value mapping is not removed from the multimap, {@link Entry#getValue} + * returns the value from the multimap, which may change over time, and {@link + * Entry#setValue} modifies that value. Removing the mapping from the + * multimap does not alter the value returned by {@code getValue()}, though a + * subsequent {@code setValue()} call won't update the multimap but will lead + * to a revised value being returned by {@code getValue()}. + */ + public Collection> entries() { + Collection> result = entries; + if (result == null) { + entries = result = new AbstractCollection>() { + @Override public int size() { + return keyCount.size(); + } + + @Override public Iterator> iterator() { + final Iterator> nodes = new NodeIterator(); + return new Iterator>() { + public boolean hasNext() { + return nodes.hasNext(); + } + + public Entry next() { + final Node node = nodes.next(); + return new AbstractMapEntry() { + @Override public K getKey() { + return node.key; + } + @Override public V getValue() { + return node.value; + } + @Override public V setValue(V value) { + V oldValue = node.value; + node.value = value; + return oldValue; + } + }; + } + + public void remove() { + nodes.remove(); + } + }; + } + }; + } + return result; + } + + private class AsMapEntries extends AbstractSet>> { + + // TODO: Override contains() and remove() for better performance. + + @Override public int size() { + return keyCount.elementSet().size(); + } + + @Override public Iterator>> iterator() { + final Iterator keyIterator = new DistinctKeyIterator(); + return new Iterator>>() { + public boolean hasNext() { + return keyIterator.hasNext(); + } + + public Entry> next() { + final K key = keyIterator.next(); + return new AbstractMapEntry>() { + @Override public K getKey() { + return key; + } + + @Override public Collection getValue() { + return LinkedListMultimap.this.get(key); + } + }; + } + + public void remove() { + keyIterator.remove(); + } + }; + } + } + + private transient Map> map; + + public Map> asMap() { + Map> result = map; + if (result == null) { + map = result = new AbstractMap>() { + Set>> entrySet; + + @Override public Set>> entrySet() { + Set>> result = entrySet; + if (result == null) { + entrySet = result = new AsMapEntries(); + } + return result; + } + + // The following methods are included for performance. + + @Override public boolean containsKey(@Nullable Object key) { + return LinkedListMultimap.this.containsKey(key); + } + + @SuppressWarnings("unchecked") + @Override public Collection get(@Nullable Object key) { + Collection collection = LinkedListMultimap.this.get((K) key); + return collection.isEmpty() ? null : collection; + } + + @Override public Collection remove(@Nullable Object key) { + Collection collection = removeAll(key); + return collection.isEmpty() ? null : collection; + } + }; + } + + return result; + } + + // Comparison and hashing + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + */ + @Override public boolean equals(@Nullable Object other) { + if (other == this) { + return true; + } + if (other instanceof Multimap) { + Multimap that = (Multimap) other; + return this.asMap().equals(that.asMap()); + } + return false; + } + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + */ + @Override public int hashCode() { + return asMap().hashCode(); + } + + /** + * Returns a string representation of the multimap, generated by calling + * {@code toString} on the map returned by {@link Multimap#asMap}. + * + * @return a string representation of the multimap + */ + @Override public String toString() { + return asMap().toString(); + } + + /** + * @serialData the number of distinct keys, and then for each distinct key: + * the first key, the number of values for that key, and the key's values, + * followed by successive keys and values from the entries() ordering + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeInt(size()); + for (Entry entry : entries()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyCount = LinkedHashMultiset.create(); + keyToKeyHead = Maps.newHashMap(); + keyToKeyTail = Maps.newHashMap(); + int size = stream.readInt(); + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeObject + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + V value = (V) stream.readObject(); + put(key, value); + } + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ListMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ListMultimap.java new file mode 100644 index 00000000000..e3e580cc5c7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ListMultimap.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * A {@code Multimap} that can hold duplicate key-value pairs and that maintains + * the insertion ordering of values for a given key. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link List} of values. Though the method signature doesn't say + * so explicitly, the map returned by {@link #asMap} has {@code List} values. + * + * @author Jared Levy + */ +@GwtCompatible +public interface ListMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + List get(@Nullable K key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + List removeAll(@Nullable Object key); + + /** + * {@inheritDoc} + * + *

Because the values for a given key may have duplicates and follow the + * insertion ordering, this method returns a {@link List}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + List replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link List} values. + */ + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code ListMultimap} instances are equal if, for each key, they + * contain the same values in the same order. If the value orderings disagree, + * the multimaps will not be considered equal. + */ + boolean equals(@Nullable Object obj); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Lists.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Lists.java new file mode 100644 index 00000000000..164bd2c9da8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Lists.java @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import org.elasticsearch.util.gcommon.base.Function; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkElementIndex; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.AbstractSequentialList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link List} instances. Also see this + * class's counterparts {@link Sets} and {@link Maps}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +@GwtCompatible +public final class Lists { + private Lists() {} + + // ArrayList + + /** + * Creates a mutable, empty {@code ArrayList} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableList#of()} instead. + * + * @return a new, empty {@code ArrayList} + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList() { + return new ArrayList(); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given + * elements. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableList#of(Object[])} instead. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code ArrayList} containing those elements + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(E... elements) { + checkNotNull(elements); // for GWT + // Avoid integer overflow when a large array is passed in + int capacity = computeArrayListCapacity(elements.length); + ArrayList list = new ArrayList(capacity); + Collections.addAll(list, elements); + return list; + } + + @VisibleForTesting static int computeArrayListCapacity(int arraySize) { + checkArgument(arraySize >= 0); + + // TODO: Figure out the right behavior, and document it + return (int) Math.min(5L + arraySize + (arraySize / 10), Integer.MAX_VALUE); + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given + * elements. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableList#copyOf(Iterator)} instead. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code ArrayList} containing those elements + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(Iterable elements) { + checkNotNull(elements); // for GWT + // Let ArrayList's sizing logic work, if possible + if (elements instanceof Collection) { + @SuppressWarnings("unchecked") + Collection collection = (Collection) elements; + return new ArrayList(collection); + } else { + return newArrayList(elements.iterator()); + } + } + + /** + * Creates a mutable {@code ArrayList} instance containing the given + * elements. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableList#copyOf(Iterator)} instead. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code ArrayList} containing those elements + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayList(Iterator elements) { + checkNotNull(elements); // for GWT + ArrayList list = newArrayList(); + while (elements.hasNext()) { + list.add(elements.next()); + } + return list; + } + + /** + * Creates an {@code ArrayList} instance backed by an array of the + * exact size specified; equivalent to + * {@link ArrayList#ArrayList(int)}. + * + *

Note: if you know the exact size your list will be, consider + * using a fixed-size list ({@link Arrays#asList(Object[])}) or an {@link + * ImmutableList} instead of a growable {@link ArrayList}. + * + *

Note: If you have only an estimate of the eventual size of + * the list, consider padding this estimate by a suitable amount, or simply + * use {@link #newArrayListWithExpectedSize(int)} instead. + * + * @param initialArraySize the exact size of the initial backing array for + * the returned array list ({@code ArrayList} documentation calls this + * value the "capacity") + * @return a new, empty {@code ArrayList} which is guaranteed not to resize + * itself unless its size reaches {@code initialArraySize + 1} + * @throws IllegalArgumentException if {@code initialArraySize} is negative + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayListWithCapacity( + int initialArraySize) { + return new ArrayList(initialArraySize); + } + + /** + * Creates an {@code ArrayList} instance sized appropriately to hold an + * estimated number of elements without resizing. A small amount of + * padding is added in case the estimate is low. + * + *

Note: If you know the exact number of elements the list + * will hold, or prefer to calculate your own amount of padding, refer to + * {@link #newArrayListWithCapacity(int)}. + * + * @param estimatedSize an estimate of the eventual {@link List#size()} of + * the new list + * @return a new, empty {@code ArrayList}, sized appropriately to hold the + * estimated number of elements + * @throws IllegalArgumentException if {@code estimatedSize} is negative + */ + @GwtCompatible(serializable = true) + public static ArrayList newArrayListWithExpectedSize( + int estimatedSize) { + return new ArrayList(computeArrayListCapacity(estimatedSize)); + } + + // LinkedList + + /** + * Creates an empty {@code LinkedList} instance. + * + *

Note: if you need an immutable empty {@link List}, use + * {@link Collections#emptyList} instead. + * + * @return a new, empty {@code LinkedList} + */ + @GwtCompatible(serializable = true) + public static LinkedList newLinkedList() { + return new LinkedList(); + } + + /** + * Creates a {@code LinkedList} instance containing the given elements. + * + * @param elements the elements that the list should contain, in order + * @return a new {@code LinkedList} containing those elements + */ + @GwtCompatible(serializable = true) + public static LinkedList newLinkedList( + Iterable elements) { + LinkedList list = newLinkedList(); + for (E element : elements) { + list.add(element); + } + return list; + } + + /** + * Returns an unmodifiable list containing the specified first element and + * backed by the specified array of additional elements. Changes to the {@code + * rest} array will be reflected in the returned list. Unlike {@link + * Arrays#asList}, the returned list is unmodifiable. + * + *

This is useful when a varargs method needs to use a signature such as + * {@code (Foo firstFoo, Foo... moreFoos)}, in order to avoid overload + * ambiguity or to enforce a minimum argument count. + * + *

The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static List asList(@Nullable E first, E[] rest) { + return new OnePlusArrayList(first, rest); + } + + /** @see Lists#asList(Object, Object[]) */ + private static class OnePlusArrayList extends AbstractList + implements Serializable, RandomAccess { + final E first; + final E[] rest; + + OnePlusArrayList(@Nullable E first, E[] rest) { + this.first = first; + this.rest = checkNotNull(rest); + } + @Override public int size() { + return rest.length + 1; + } + @Override public E get(int index) { + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size()); + return (index == 0) ? first : rest[index - 1]; + } + private static final long serialVersionUID = 0; + } + + /** + * Returns an unmodifiable list containing the specified first and second + * element, and backed by the specified array of additional elements. Changes + * to the {@code rest} array will be reflected in the returned list. Unlike + * {@link Arrays#asList}, the returned list is unmodifiable. + * + *

This is useful when a varargs method needs to use a signature such as + * {@code (Foo firstFoo, Foo secondFoo, Foo... moreFoos)}, in order to avoid + * overload ambiguity or to enforce a minimum argument count. + * + *

The returned list is serializable and implements {@link RandomAccess}. + * + * @param first the first element + * @param second the second element + * @param rest an array of additional elements, possibly empty + * @return an unmodifiable list containing the specified elements + */ + public static List asList( + @Nullable E first, @Nullable E second, E[] rest) { + return new TwoPlusArrayList(first, second, rest); + } + + /** @see Lists#asList(Object, Object, Object[]) */ + private static class TwoPlusArrayList extends AbstractList + implements Serializable, RandomAccess { + final E first; + final E second; + final E[] rest; + + TwoPlusArrayList(@Nullable E first, @Nullable E second, E[] rest) { + this.first = first; + this.second = second; + this.rest = checkNotNull(rest); + } + @Override public int size() { + return rest.length + 2; + } + @Override public E get(int index) { + switch (index) { + case 0: + return first; + case 1: + return second; + default: + // check explicitly so the IOOBE will have the right message + checkElementIndex(index, size()); + return rest[index - 2]; + } + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a list that applies {@code function} to each element of {@code + * fromList}. The returned list is a transformed view of {@code fromList}; + * changes to {@code fromList} will be reflected in the returned list and vice + * versa. + * + *

Since functions are not reversible, the transform is one-way and new + * items cannot be stored in the returned list. The {@code add}, + * {@code addAll} and {@code set} methods are unsupported in the returned + * list. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned list to be a view, but it means that the function will be + * applied many times for bulk operations like {@link List#contains} and + * {@link List#hashCode}. For this to perform well, {@code function} should be + * fast. To avoid lazy evaluation when the returned list doesn't need to be a + * view, copy the returned list into a new list of your choosing. + * + *

If {@code fromList} implements {@link RandomAccess}, so will the + * returned list. The returned list always implements {@link Serializable}, + * but serialization will succeed only when {@code fromList} and + * {@code function} are serializable. The returned list is threadsafe if the + * supplied list and function are. + */ + public static List transform( + List fromList, Function function) { + return (fromList instanceof RandomAccess) + ? new TransformingRandomAccessList(fromList, function) + : new TransformingSequentialList(fromList, function); + } + + /** + * Implementation of a sequential transforming list. + * + * @see Lists#transform + */ + private static class TransformingSequentialList + extends AbstractSequentialList implements Serializable { + final List fromList; + final Function function; + + TransformingSequentialList( + List fromList, Function function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + /** + * The default implementation inherited is based on iteration and removal of + * each element which can be overkill. That's why we forward this call + * directly to the backing list. + */ + @Override public void clear() { + fromList.clear(); + } + @Override public int size() { + return fromList.size(); + } + @Override public ListIterator listIterator(final int index) { + final ListIterator delegate = fromList.listIterator(index); + return new ListIterator() { + public void add(T e) { + throw new UnsupportedOperationException(); + } + + public boolean hasNext() { + return delegate.hasNext(); + } + + public boolean hasPrevious() { + return delegate.hasPrevious(); + } + + public T next() { + return function.apply(delegate.next()); + } + + public int nextIndex() { + return delegate.nextIndex(); + } + + public T previous() { + return function.apply(delegate.previous()); + } + + public int previousIndex() { + return delegate.previousIndex(); + } + + public void remove() { + delegate.remove(); + } + + public void set(T e) { + throw new UnsupportedOperationException("not supported"); + } + }; + } + + private static final long serialVersionUID = 0; + } + + /** + * Implementation of a transforming random access list. We try to make as many + * of these methods pass-through to the source list as possible so that the + * performance characteristics of the source list and transformed list are + * similar. + * + * @see Lists#transform + */ + private static class TransformingRandomAccessList + extends AbstractList implements RandomAccess, Serializable { + final List fromList; + final Function function; + + TransformingRandomAccessList( + List fromList, Function function) { + this.fromList = checkNotNull(fromList); + this.function = checkNotNull(function); + } + @Override public void clear() { + fromList.clear(); + } + @Override public T get(int index) { + return function.apply(fromList.get(index)); + } + @Override public boolean isEmpty() { + return fromList.isEmpty(); + } + @Override public T remove(int index) { + return function.apply(fromList.remove(index)); + } + @Override public int size() { + return fromList.size(); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns consecutive {@linkplain List#subList(int, int) sublists} of a list, + * each of the same size (the final list may be smaller). For example, + * partitioning a list containing {@code [a, b, c, d, e]} with a partition + * size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer list containing + * two inner lists of three and two elements, all in the original order. + * + *

The outer list is unmodifiable, but reflects the latest state of the + * source list. The inner lists are sublist views of the original list, + * produced on demand using {@link List#subList(int, int)}, and are subject + * to all the usual caveats about modification as explained in that API. + * + * @param list the list to return consecutive sublists of + * @param size the desired size of each sublist (the last may be + * smaller) + * @return a list of consecutive sublists + * @throws IllegalArgumentException if {@code partitionSize} is nonpositive + */ + public static List> partition(List list, int size) { + checkNotNull(list); + checkArgument(size > 0); + return (list instanceof RandomAccess) + ? new RandomAccessPartition(list, size) + : new Partition(list, size); + } + + private static class Partition extends AbstractList> { + final List list; + final int size; + + Partition(List list, int size) { + this.list = list; + this.size = size; + } + + @Override public List get(int index) { + int listSize = size(); + checkElementIndex(index, listSize); + int start = index * size; + int end = Math.min(start + size, list.size()); + return Platform.subList(list, start, end); + } + + @Override public int size() { + return (list.size() + size - 1) / size; + } + + @Override public boolean isEmpty() { + return list.isEmpty(); + } + } + + private static class RandomAccessPartition extends Partition + implements RandomAccess { + RandomAccessPartition(List list, int size) { + super(list, size); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapConstraint.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapConstraint.java new file mode 100644 index 00000000000..cdee73c8eef --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapConstraint.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import javax.annotation.Nullable; + +@GwtCompatible +interface MapConstraint { + /** + * Throws a suitable {@code RuntimeException} if the specified key or value is + * illegal. Typically this is either a {@link NullPointerException}, an + * {@link IllegalArgumentException}, or a {@link ClassCastException}, though + * an application-specific exception class may be used if appropriate. + */ + void checkKeyValue(@Nullable K key, @Nullable V value); + + /** + * Returns a brief human readable description of this constraint, such as + * "Not null". + */ + String toString(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapDifference.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapDifference.java new file mode 100644 index 00000000000..baba444632e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapDifference.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * An object representing the differences between two maps. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public interface MapDifference { + /** + * Returns {@code true} if there are no differences between the two maps; + * that is, if the maps are equal. + */ + boolean areEqual(); + + /** + * Returns an unmodifiable map containing the entries from the left map whose + * keys are not present in the right map. + */ + Map entriesOnlyOnLeft(); + + /** + * Returns an unmodifiable map containing the entries from the right map whose + * keys are not present in the left map. + */ + Map entriesOnlyOnRight(); + + /** + * Returns an unmodifiable map containing the entries that appear in both + * maps; that is, the intersection of the two maps. + */ + Map entriesInCommon(); + + /** + * Returns an unmodifiable map describing keys that appear in both maps, but + * with different values. + */ + Map> entriesDiffering(); + + /** + * Compares the specified object with this instance for equality. Returns + * {@code true} if the given object is also a {@code MapDifference} and the + * values returned by the {@link #entriesOnlyOnLeft()}, {@link + * #entriesOnlyOnRight()}, {@link #entriesInCommon()} and {@link + * #entriesDiffering()} of the two instances are equal. + */ + boolean equals(@Nullable Object object); + + /** + * Returns the hash code for this instance. This is defined as the hash code + * of

   {@code
+   *
+   *   Arrays.asList(entriesOnlyOnLeft(), entriesOnlyOnRight(),
+   *       entriesInCommon(), entriesDiffering())}
+ */ + int hashCode(); + + /** + * A difference between the mappings from two maps with the same key. The + * {@code leftValue()} and {@code rightValue} are not equal, and one but not + * both of them may be null. + */ + interface ValueDifference { + /** + * Returns the value from the left map (possibly null). + */ + V leftValue(); + + /** + * Returns the value from the right map (possibly null). + */ + V rightValue(); + + /** + * Two instances are considered equal if their {@link #leftValue()} + * values are equal and their {@link #rightValue()} values are also equal. + */ + @Override boolean equals(@Nullable Object other); + + /** + * The hash code equals the value + * {@code Arrays.asList(leftValue(), rightValue()).hashCode()}. + */ + @Override int hashCode(); + } + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapMaker.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapMaker.java new file mode 100644 index 00000000000..8f7f739e8bc --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MapMaker.java @@ -0,0 +1,1118 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; +import org.elasticsearch.util.gcommon.base.FinalizableReferenceQueue; +import org.elasticsearch.util.gcommon.base.FinalizableSoftReference; +import org.elasticsearch.util.gcommon.base.FinalizableWeakReference; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.collect.CustomConcurrentHashMap.ComputingStrategy; +import org.elasticsearch.util.gcommon.collect.CustomConcurrentHashMap.Internals; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +/** + * A {@link ConcurrentMap} builder, providing any combination of these + * features: {@linkplain SoftReference soft} or {@linkplain WeakReference + * weak} keys, soft or weak values, timed expiration, and on-demand + * computation of values. Usage example:
 {@code
+ *
+ *   ConcurrentMap graphs = new MapMaker()
+ *       .concurrencyLevel(32)
+ *       .softKeys()
+ *       .weakValues()
+ *       .expiration(30, TimeUnit.MINUTES)
+ *       .makeComputingMap(
+ *           new Function() {
+ *             public Graph apply(Key key) {
+ *               return createExpensiveGraph(key);
+ *             }
+ *           });}
+ * + * These features are all optional; {@code new MapMaker().makeMap()} + * returns a valid concurrent map that behaves exactly like a + * {@link ConcurrentHashMap}. + * + * The returned map is implemented as a hash table with similar performance + * characteristics to {@link ConcurrentHashMap}. It supports all optional + * operations of the {@code ConcurrentMap} interface. It does not permit + * null keys or values. It is serializable; however, serializing a map that + * uses soft or weak references can give unpredictable results. + * + *

Note: by default, the returned map uses equality comparisons + * (the {@link Object#equals(Object) equals} method) to determine equality + * for keys or values. However, if {@link #weakKeys()} or {@link + * #softKeys()} was specified, the map uses identity ({@code ==}) + * comparisons instead for keys. Likewise, if {@link #weakValues()} or + * {@link #softValues()} was specified, the map uses identity comparisons + * for values. + * + *

The returned map has weakly consistent iteration: an iterator + * over one of the map's view collections may reflect some, all or none of + * the changes made to the map after the iterator was created. + * + *

An entry whose key or value is reclaimed by the garbage collector + * immediately disappears from the map. (If the default settings of strong + * keys and strong values are used, this will never happen.) The client can + * never observe a partially-reclaimed entry. Any {@link java.util.Map.Entry} + * instance retrieved from the map's {@linkplain Map#entrySet() entry set} + * is snapshot of that entry's state at the time of retrieval. + * + *

{@code new MapMaker().weakKeys().makeMap()} can almost always be + * used as a drop-in replacement for {@link java.util.WeakHashMap}, adding + * concurrency, asynchronous cleanup, identity-based equality for keys, and + * great flexibility. + * + * @author Bob Lee + * @author Kevin Bourrillion + */ +@GwtCompatible(emulated = true) +public final class MapMaker { + private Strength keyStrength = Strength.STRONG; + private Strength valueStrength = Strength.STRONG; + private long expirationNanos = 0; + private boolean useCustomMap; + private final CustomConcurrentHashMap.Builder builder + = new CustomConcurrentHashMap.Builder(); + + /** + * Constructs a new {@code MapMaker} instance with default settings, + * including strong keys, strong values, and no automatic expiration. + */ + public MapMaker() {} + + /** + * Sets a custom initial capacity (defaults to 16). Resizing this or + * any other kind of hash table is a relatively slow operation, so, + * when possible, it is a good idea to provide estimates of expected + * table sizes. + * + * @throws IllegalArgumentException if {@code initialCapacity} is + * negative + * @throws IllegalStateException if an initial capacity was already set + */ + public MapMaker initialCapacity(int initialCapacity) { + builder.initialCapacity(initialCapacity); + return this; + } + + /** + * Guides the allowed concurrency among update operations. Used as a + * hint for internal sizing. The table is internally partitioned to try + * to permit the indicated number of concurrent updates without + * contention. Because placement in hash tables is essentially random, + * the actual concurrency will vary. Ideally, you should choose a value + * to accommodate as many threads as will ever concurrently modify the + * table. Using a significantly higher value than you need can waste + * space and time, and a significantly lower value can lead to thread + * contention. But overestimates and underestimates within an order of + * magnitude do not usually have much noticeable impact. A value of one + * is appropriate when it is known that only one thread will modify and + * all others will only read. Defaults to 16. + * + * @throws IllegalArgumentException if {@code concurrencyLevel} is + * nonpositive + * @throws IllegalStateException if a concurrency level was already set + */ + @GwtIncompatible("java.util.concurrent.ConcurrentHashMap concurrencyLevel") + public MapMaker concurrencyLevel(int concurrencyLevel) { + builder.concurrencyLevel(concurrencyLevel); + return this; + } + + /** + * Specifies that each key (not value) stored in the map should be + * wrapped in a {@link WeakReference} (by default, strong references + * are used). + * + *

Note: the map will use identity ({@code ==}) comparison + * to determine equality of weak keys, which may not behave as you expect. + * For example, storing a key in the map and then attempting a lookup + * using a different but {@link Object#equals(Object) equals}-equivalent + * key will always fail. + * + * @throws IllegalStateException if the key strength was already set + * @see WeakReference + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public MapMaker weakKeys() { + return setKeyStrength(Strength.WEAK); + } + + /** + * Specifies that each key (not value) stored in the map should be + * wrapped in a {@link SoftReference} (by default, strong references + * are used). + * + *

Note: the map will use identity ({@code ==}) comparison + * to determine equality of soft keys, which may not behave as you expect. + * For example, storing a key in the map and then attempting a lookup + * using a different but {@link Object#equals(Object) equals}-equivalent + * key will always fail. + * + * @throws IllegalStateException if the key strength was already set + * @see SoftReference + */ + @GwtIncompatible("java.lang.ref.SoftReference") + public MapMaker softKeys() { + return setKeyStrength(Strength.SOFT); + } + + private MapMaker setKeyStrength(Strength strength) { + if (keyStrength != Strength.STRONG) { + throw new IllegalStateException("Key strength was already set to " + + keyStrength + "."); + } + keyStrength = strength; + useCustomMap = true; + return this; + } + + /** + * Specifies that each value (not key) stored in the map should be + * wrapped in a {@link WeakReference} (by default, strong references + * are used). + * + *

Weak values will be garbage collected once they are weakly + * reachable. This makes them a poor candidate for caching; consider + * {@link #softValues()} instead. + * + *

Note: the map will use identity ({@code ==}) comparison + * to determine equality of weak values. This will notably impact + * the behavior of {@link Map#containsValue(Object) containsValue}, + * {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)}, + * and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}. + * + * @throws IllegalStateException if the key strength was already set + * @see WeakReference + */ + @GwtIncompatible("java.lang.ref.WeakReference") + public MapMaker weakValues() { + return setValueStrength(Strength.WEAK); + } + + /** + * Specifies that each value (not key) stored in the map should be + * wrapped in a {@link SoftReference} (by default, strong references + * are used). + * + *

Soft values will be garbage collected in response to memory + * demand, and in a least-recently-used manner. This makes them a + * good candidate for caching. + * + *

Note: the map will use identity ({@code ==}) comparison + * to determine equality of soft values. This will notably impact + * the behavior of {@link Map#containsValue(Object) containsValue}, + * {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)}, + * and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}. + * + * @throws IllegalStateException if the value strength was already set + * @see SoftReference + */ + @GwtIncompatible("java.lang.ref.SoftReference") + public MapMaker softValues() { + return setValueStrength(Strength.SOFT); + } + + private MapMaker setValueStrength(Strength strength) { + if (valueStrength != Strength.STRONG) { + throw new IllegalStateException("Value strength was already set to " + + valueStrength + "."); + } + valueStrength = strength; + useCustomMap = true; + return this; + } + + /** + * Specifies that each entry should be automatically removed from the + * map once a fixed duration has passed since the entry's creation. + * + * @param duration the length of time after an entry is created that it + * should be automatically removed + * @param unit the unit that {@code duration} is expressed in + * @throws IllegalArgumentException if {@code duration} is not positive + * @throws IllegalStateException if the expiration time was already set + */ + public MapMaker expiration(long duration, TimeUnit unit) { + if (expirationNanos != 0) { + throw new IllegalStateException("expiration time of " + + expirationNanos + " ns was already set"); + } + if (duration <= 0) { + throw new IllegalArgumentException("invalid duration: " + duration); + } + this.expirationNanos = unit.toNanos(duration); + useCustomMap = true; + return this; + } + + /** + * Builds the final map, without on-demand computation of values. This method + * does not alter the state of this {@code MapMaker} instance, so it can be + * invoked again to create multiple independent maps. + * + * @param the type of keys to be stored in the returned map + * @param the type of values to be stored in the returned map + * @return a concurrent map having the requested features + */ + public ConcurrentMap makeMap() { + return useCustomMap + ? new StrategyImpl(this).map + : new ConcurrentHashMap(builder.getInitialCapacity(), + 0.75f, builder.getConcurrencyLevel()); + } + + /** + * Builds a map that supports atomic, on-demand computation of values. {@link + * Map#get} either returns an already-computed value for the given key, + * atomically computes it using the supplied function, or, if another thread + * is currently computing the value for this key, simply waits for that thread + * to finish and returns its computed value. Note that the function may be + * executed concurrently by multiple threads, but only for distinct keys. + * + *

If an entry's value has not finished computing yet, query methods + * besides {@code get} return immediately as if an entry doesn't exist. In + * other words, an entry isn't externally visible until the value's + * computation completes. + * + *

{@link Map#get} on the returned map will never return {@code null}. It + * may throw: + * + *

    + *
  • {@link NullPointerException} if the key is null or the computing + * function returns null + *
  • {@link ComputationException} if an exception was thrown by the + * computing function. If that exception is already of type {@link + * ComputationException}, it is propagated directly; otherwise it is + * wrapped. + *
+ * + *

Note: Callers of {@code get} must ensure that the key + * argument is of type {@code K}. The {@code get} method accepts {@code + * Object}, so the key type is not checked at compile time. Passing an object + * of a type other than {@code K} can result in that object being unsafely + * passed to the computing function as type {@code K}, and unsafely stored in + * the map. + * + *

If {@link Map#put} is called before a computation completes, other + * threads waiting on the computation will wake up and return the stored + * value. When the computation completes, its new result will overwrite the + * value that was put in the map manually. + * + *

This method does not alter the state of this {@code MapMaker} instance, + * so it can be invoked again to create multiple independent maps. + */ + public ConcurrentMap makeComputingMap( + Function computingFunction) { + return new StrategyImpl(this, computingFunction).map; + } + + // Remainder of this file is private implementation details + + private enum Strength { + WEAK { + @Override boolean equal(Object a, Object b) { + return a == b; + } + @Override int hash(Object o) { + return System.identityHashCode(o); + } + @Override ValueReference referenceValue( + ReferenceEntry entry, V value) { + return new WeakValueReference(value, entry); + } + @Override ReferenceEntry newEntry( + Internals> internals, K key, + int hash, ReferenceEntry next) { + return (next == null) + ? new WeakEntry(internals, key, hash) + : new LinkedWeakEntry(internals, key, hash, next); + } + @Override ReferenceEntry copyEntry( + K key, ReferenceEntry original, + ReferenceEntry newNext) { + WeakEntry from = (WeakEntry) original; + return (newNext == null) + ? new WeakEntry(from.internals, key, from.hash) + : new LinkedWeakEntry( + from.internals, key, from.hash, newNext); + } + }, + + SOFT { + @Override boolean equal(Object a, Object b) { + return a == b; + } + @Override int hash(Object o) { + return System.identityHashCode(o); + } + @Override ValueReference referenceValue( + ReferenceEntry entry, V value) { + return new SoftValueReference(value, entry); + } + @Override ReferenceEntry newEntry( + Internals> internals, K key, + int hash, ReferenceEntry next) { + return (next == null) + ? new SoftEntry(internals, key, hash) + : new LinkedSoftEntry(internals, key, hash, next); + } + @Override ReferenceEntry copyEntry( + K key, ReferenceEntry original, + ReferenceEntry newNext) { + SoftEntry from = (SoftEntry) original; + return (newNext == null) + ? new SoftEntry(from.internals, key, from.hash) + : new LinkedSoftEntry( + from.internals, key, from.hash, newNext); + } + }, + + STRONG { + @Override boolean equal(Object a, Object b) { + return a.equals(b); + } + @Override int hash(Object o) { + return o.hashCode(); + } + @Override ValueReference referenceValue( + ReferenceEntry entry, V value) { + return new StrongValueReference(value); + } + @Override ReferenceEntry newEntry( + Internals> internals, K key, + int hash, ReferenceEntry next) { + return (next == null) + ? new StrongEntry(internals, key, hash) + : new LinkedStrongEntry( + internals, key, hash, next); + } + @Override ReferenceEntry copyEntry( + K key, ReferenceEntry original, + ReferenceEntry newNext) { + StrongEntry from = (StrongEntry) original; + return (newNext == null) + ? new StrongEntry(from.internals, key, from.hash) + : new LinkedStrongEntry( + from.internals, key, from.hash, newNext); + } + }; + + /** + * Determines if two keys or values are equal according to this + * strength strategy. + */ + abstract boolean equal(Object a, Object b); + + /** + * Hashes a key according to this strategy. + */ + abstract int hash(Object o); + + /** + * Creates a reference for the given value according to this value + * strength. + */ + abstract ValueReference referenceValue( + ReferenceEntry entry, V value); + + /** + * Creates a new entry based on the current key strength. + */ + abstract ReferenceEntry newEntry( + Internals> internals, K key, + int hash, ReferenceEntry next); + + /** + * Creates a new entry and copies the value and other state from an + * existing entry. + */ + abstract ReferenceEntry copyEntry(K key, + ReferenceEntry original, ReferenceEntry newNext); + } + + private static class StrategyImpl implements Serializable, + ComputingStrategy> { + final Strength keyStrength; + final Strength valueStrength; + final ConcurrentMap map; + final long expirationNanos; + Internals> internals; + + StrategyImpl(MapMaker maker) { + this.keyStrength = maker.keyStrength; + this.valueStrength = maker.valueStrength; + this.expirationNanos = maker.expirationNanos; + + map = maker.builder.buildMap(this); + } + + StrategyImpl( + MapMaker maker, Function computer) { + this.keyStrength = maker.keyStrength; + this.valueStrength = maker.valueStrength; + this.expirationNanos = maker.expirationNanos; + + map = maker.builder.buildComputingMap(this, computer); + } + + public void setValue(ReferenceEntry entry, V value) { + setValueReference( + entry, valueStrength.referenceValue(entry, value)); + if (expirationNanos > 0) { + scheduleRemoval(entry.getKey(), value); + } + } + + void scheduleRemoval(K key, V value) { + /* + * TODO: Keep weak reference to map, too. Build a priority + * queue out of the entries themselves instead of creating a + * task per entry. Then, we could have one recurring task per + * map (which would clean the entire map and then reschedule + * itself depending upon when the next expiration comes). We + * also want to avoid removing an entry prematurely if the + * entry was set to the same value again. + */ + final WeakReference keyReference = new WeakReference(key); + final WeakReference valueReference = new WeakReference(value); + ExpirationTimer.instance.schedule( + new TimerTask() { + @Override public void run() { + K key = keyReference.get(); + if (key != null) { + // Remove if the value is still the same. + map.remove(key, valueReference.get()); + } + } + }, TimeUnit.NANOSECONDS.toMillis(expirationNanos)); + } + + public boolean equalKeys(K a, Object b) { + return keyStrength.equal(a, b); + } + + public boolean equalValues(V a, Object b) { + return valueStrength.equal(a, b); + } + + public int hashKey(Object key) { + return keyStrength.hash(key); + } + + public K getKey(ReferenceEntry entry) { + return entry.getKey(); + } + + public int getHash(ReferenceEntry entry) { + return entry.getHash(); + } + + public ReferenceEntry newEntry( + K key, int hash, ReferenceEntry next) { + return keyStrength.newEntry(internals, key, hash, next); + } + + public ReferenceEntry copyEntry(K key, + ReferenceEntry original, ReferenceEntry newNext) { + ValueReference valueReference = original.getValueReference(); + if (valueReference == COMPUTING) { + ReferenceEntry newEntry + = newEntry(key, original.getHash(), newNext); + newEntry.setValueReference( + new FutureValueReference(original, newEntry)); + return newEntry; + } else { + ReferenceEntry newEntry + = newEntry(key, original.getHash(), newNext); + newEntry.setValueReference(valueReference.copyFor(newEntry)); + return newEntry; + } + } + + /** + * Waits for a computation to complete. Returns the result of the + * computation or null if none was available. + */ + public V waitForValue(ReferenceEntry entry) + throws InterruptedException { + ValueReference valueReference = entry.getValueReference(); + if (valueReference == COMPUTING) { + synchronized (entry) { + while ((valueReference = entry.getValueReference()) + == COMPUTING) { + entry.wait(); + } + } + } + return valueReference.waitForValue(); + } + + /** + * Used by CustomConcurrentHashMap to retrieve values. Returns null + * instead of blocking or throwing an exception. + */ + public V getValue(ReferenceEntry entry) { + ValueReference valueReference = entry.getValueReference(); + return valueReference.get(); + } + + public V compute(K key, final ReferenceEntry entry, + Function computer) { + V value; + try { + value = computer.apply(key); + } catch (ComputationException e) { + // if computer has thrown a computation exception, propagate rather + // than wrap + setValueReference(entry, + new ComputationExceptionReference(e.getCause())); + throw e; + } catch (Throwable t) { + setValueReference( + entry, new ComputationExceptionReference(t)); + throw new ComputationException(t); + } + + if (value == null) { + String message + = computer + " returned null for key " + key + "."; + setValueReference( + entry, new NullOutputExceptionReference(message)); + throw new NullOutputException(message); + } else { + setValue(entry, value); + } + return value; + } + + /** + * Sets the value reference on an entry and notifies waiting + * threads. + */ + void setValueReference(ReferenceEntry entry, + ValueReference valueReference) { + boolean notifyOthers = (entry.getValueReference() == COMPUTING); + entry.setValueReference(valueReference); + if (notifyOthers) { + synchronized (entry) { + entry.notifyAll(); + } + } + } + + /** + * Points to an old entry where a value is being computed. Used to + * support non-blocking copying of entries during table expansion, + * removals, etc. + */ + private class FutureValueReference implements ValueReference { + final ReferenceEntry original; + final ReferenceEntry newEntry; + + FutureValueReference( + ReferenceEntry original, ReferenceEntry newEntry) { + this.original = original; + this.newEntry = newEntry; + } + + public V get() { + boolean success = false; + try { + V value = original.getValueReference().get(); + success = true; + return value; + } finally { + if (!success) { + removeEntry(); + } + } + } + + public ValueReference copyFor(ReferenceEntry entry) { + return new FutureValueReference(original, entry); + } + + public V waitForValue() throws InterruptedException { + boolean success = false; + try { + // assert that key != null + V value = StrategyImpl.this.waitForValue(original); + success = true; + return value; + } finally { + if (!success) { + removeEntry(); + } + } + } + + /** + * Removes the entry in the event of an exception. Ideally, + * we'd clean up as soon as the computation completes, but we + * can't do that without keeping a reference to this entry from + * the original. + */ + void removeEntry() { + internals.removeEntry(newEntry); + } + } + + public ReferenceEntry getNext( + ReferenceEntry entry) { + return entry.getNext(); + } + + public void setInternals( + Internals> internals) { + this.internals = internals; + } + + private static final long serialVersionUID = 0; + + private void writeObject(ObjectOutputStream out) + throws IOException { + // Custom serialization code ensures that the key and value + // strengths are written before the map. We'll need them to + // deserialize the map entries. + out.writeObject(keyStrength); + out.writeObject(valueStrength); + out.writeLong(expirationNanos); + + // TODO: It is possible for the strategy to try to use the map + // or internals during deserialization, for example, if an + // entry gets reclaimed. We could detect this case and queue up + // removals to be flushed after we deserialize the map. + out.writeObject(internals); + out.writeObject(map); + } + + /** + * Fields used during deserialization. We use a nested class so we + * don't load them until we need them. We need to use reflection to + * set final fields outside of the constructor. + */ + private static class Fields { + static final Field keyStrength = findField("keyStrength"); + static final Field valueStrength = findField("valueStrength"); + static final Field expirationNanos = findField("expirationNanos"); + static final Field internals = findField("internals"); + static final Field map = findField("map"); + + static Field findField(String name) { + try { + Field f = StrategyImpl.class.getDeclaredField(name); + f.setAccessible(true); + return f; + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + try { + Fields.keyStrength.set(this, in.readObject()); + Fields.valueStrength.set(this, in.readObject()); + Fields.expirationNanos.set(this, in.readLong()); + Fields.internals.set(this, in.readObject()); + Fields.map.set(this, in.readObject()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + } + + /** A reference to a value. */ + private interface ValueReference { + /** + * Gets the value. Does not block or throw exceptions. + */ + V get(); + + /** Creates a copy of this reference for the given entry. */ + ValueReference copyFor(ReferenceEntry entry); + + /** + * Waits for a value that may still be computing. Unlike get(), + * this method can block (in the case of FutureValueReference) or + * throw an exception. + */ + V waitForValue() throws InterruptedException; + } + + private static final ValueReference COMPUTING + = new ValueReference() { + public Object get() { + return null; + } + public ValueReference copyFor( + ReferenceEntry entry) { + throw new AssertionError(); + } + public Object waitForValue() { + throw new AssertionError(); + } + }; + + /** + * Singleton placeholder that indicates a value is being computed. + */ + @SuppressWarnings("unchecked") + // Safe because impl never uses a parameter or returns any non-null value + private static ValueReference computing() { + return (ValueReference) COMPUTING; + } + + /** Used to provide null output exceptions to other threads. */ + private static class NullOutputExceptionReference + implements ValueReference { + final String message; + NullOutputExceptionReference(String message) { + this.message = message; + } + public V get() { + return null; + } + public ValueReference copyFor( + ReferenceEntry entry) { + return this; + } + public V waitForValue() { + throw new NullOutputException(message); + } + } + + /** Used to provide computation exceptions to other threads. */ + private static class ComputationExceptionReference + implements ValueReference { + final Throwable t; + ComputationExceptionReference(Throwable t) { + this.t = t; + } + public V get() { + return null; + } + public ValueReference copyFor( + ReferenceEntry entry) { + return this; + } + public V waitForValue() { + throw new AsynchronousComputationException(t); + } + } + + /** Wrapper class ensures that queue isn't created until it's used. */ + private static class QueueHolder { + static final FinalizableReferenceQueue queue + = new FinalizableReferenceQueue(); + } + + /** + * An entry in a reference map. + */ + private interface ReferenceEntry { + /** + * Gets the value reference from this entry. + */ + ValueReference getValueReference(); + + /** + * Sets the value reference for this entry. + * + * @param valueReference + */ + void setValueReference(ValueReference valueReference); + + /** + * Removes this entry from the map if its value reference hasn't + * changed. Used to clean up after values. The value reference can + * just call this method on the entry so it doesn't have to keep + * its own reference to the map. + */ + void valueReclaimed(); + + /** Gets the next entry in the chain. */ + ReferenceEntry getNext(); + + /** Gets the entry's hash. */ + int getHash(); + + /** Gets the key for this entry. */ + public K getKey(); + } + + /** + * Used for strongly-referenced keys. + */ + private static class StrongEntry implements ReferenceEntry { + final K key; + + StrongEntry(Internals> internals, K key, + int hash) { + this.internals = internals; + this.key = key; + this.hash = hash; + } + + public K getKey() { + return this.key; + } + + // The code below is exactly the same for each entry type. + + final Internals> internals; + final int hash; + volatile ValueReference valueReference = computing(); + + public ValueReference getValueReference() { + return valueReference; + } + public void setValueReference( + ValueReference valueReference) { + this.valueReference = valueReference; + } + public void valueReclaimed() { + internals.removeEntry(this, null); + } + public ReferenceEntry getNext() { + return null; + } + public int getHash() { + return hash; + } + } + + private static class LinkedStrongEntry extends StrongEntry { + + LinkedStrongEntry(Internals> internals, + K key, int hash, ReferenceEntry next) { + super(internals, key, hash); + this.next = next; + } + + final ReferenceEntry next; + + @Override public ReferenceEntry getNext() { + return next; + } + } + + /** + * Used for softly-referenced keys. + */ + private static class SoftEntry extends FinalizableSoftReference + implements ReferenceEntry { + SoftEntry(Internals> internals, K key, + int hash) { + super(key, QueueHolder.queue); + this.internals = internals; + this.hash = hash; + } + + public K getKey() { + return get(); + } + + public void finalizeReferent() { + internals.removeEntry(this); + } + + // The code below is exactly the same for each entry type. + + final Internals> internals; + final int hash; + volatile ValueReference valueReference = computing(); + + public ValueReference getValueReference() { + return valueReference; + } + public void setValueReference( + ValueReference valueReference) { + this.valueReference = valueReference; + } + public void valueReclaimed() { + internals.removeEntry(this, null); + } + public ReferenceEntry getNext() { + return null; + } + public int getHash() { + return hash; + } + } + + private static class LinkedSoftEntry extends SoftEntry { + LinkedSoftEntry(Internals> internals, + K key, int hash, ReferenceEntry next) { + super(internals, key, hash); + this.next = next; + } + + final ReferenceEntry next; + + @Override public ReferenceEntry getNext() { + return next; + } + } + + /** + * Used for weakly-referenced keys. + */ + private static class WeakEntry extends FinalizableWeakReference + implements ReferenceEntry { + WeakEntry(Internals> internals, K key, + int hash) { + super(key, QueueHolder.queue); + this.internals = internals; + this.hash = hash; + } + + public K getKey() { + return get(); + } + + public void finalizeReferent() { + internals.removeEntry(this); + } + + // The code below is exactly the same for each entry type. + + final Internals> internals; + final int hash; + volatile ValueReference valueReference = computing(); + + public ValueReference getValueReference() { + return valueReference; + } + public void setValueReference( + ValueReference valueReference) { + this.valueReference = valueReference; + } + public void valueReclaimed() { + internals.removeEntry(this, null); + } + public ReferenceEntry getNext() { + return null; + } + public int getHash() { + return hash; + } + } + + private static class LinkedWeakEntry extends WeakEntry { + LinkedWeakEntry(Internals> internals, + K key, int hash, ReferenceEntry next) { + super(internals, key, hash); + this.next = next; + } + + final ReferenceEntry next; + + @Override public ReferenceEntry getNext() { + return next; + } + } + + /** References a weak value. */ + private static class WeakValueReference + extends FinalizableWeakReference + implements ValueReference { + final ReferenceEntry entry; + + WeakValueReference(V referent, ReferenceEntry entry) { + super(referent, QueueHolder.queue); + this.entry = entry; + } + + public void finalizeReferent() { + entry.valueReclaimed(); + } + + public ValueReference copyFor( + ReferenceEntry entry) { + return new WeakValueReference(get(), entry); + } + + public V waitForValue() { + return get(); + } + } + + /** References a soft value. */ + private static class SoftValueReference + extends FinalizableSoftReference + implements ValueReference { + final ReferenceEntry entry; + + SoftValueReference(V referent, ReferenceEntry entry) { + super(referent, QueueHolder.queue); + this.entry = entry; + } + + public void finalizeReferent() { + entry.valueReclaimed(); + } + + public ValueReference copyFor( + ReferenceEntry entry) { + return new SoftValueReference(get(), entry); + } + + public V waitForValue() { + return get(); + } + } + + /** References a strong value. */ + private static class StrongValueReference + implements ValueReference { + final V referent; + + StrongValueReference(V referent) { + this.referent = referent; + } + + public V get() { + return referent; + } + + public ValueReference copyFor( + ReferenceEntry entry) { + return this; + } + + public V waitForValue() { + return get(); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Maps.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Maps.java new file mode 100644 index 00000000000..c34f621e5a3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Maps.java @@ -0,0 +1,1334 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.base.Joiner.MapJoiner; +import org.elasticsearch.util.gcommon.base.Objects; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import org.elasticsearch.util.gcommon.base.Predicate; +import org.elasticsearch.util.gcommon.base.Predicates; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link Map} instances. Also see this + * class's counterparts {@link Lists} and {@link Sets}. + * + * @author Kevin Bourrillion + * @author Mike Bostock + * @author Isaac Shum + */ +@GwtCompatible +public final class Maps { + private Maps() {} + + /** + * Creates a mutable, empty {@code HashMap} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#of()} instead. + * + *

Note: if {@code K} is an {@code enum} type, use {@link + * #newEnumMap} instead. + * + * @return a new, empty {@code HashMap} + */ + public static HashMap newHashMap() { + return new HashMap(); + } + + /** + * Creates a {@code HashMap} instance with enough capacity to hold the + * specified number of elements without rehashing. + * + * @param expectedSize the expected size + * @return a new, empty {@code HashMap} with enough + * capacity to hold {@code expectedSize} elements without rehashing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashMap newHashMapWithExpectedSize( + int expectedSize) { + /* + * The HashMap is constructed with an initialCapacity that's greater than + * expectedSize. The larger value is necessary because HashMap resizes + * its internal array if the map size exceeds loadFactor * initialCapacity. + */ + return new HashMap(capacity(expectedSize)); + } + + /** + * Returns an appropriate value for the "capacity" (in reality, "minimum + * table size") parameter of a {@link HashMap} constructor, such that the + * resulting table will be between 25% and 50% full when it contains + * {@code expectedSize} entries. + * + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + static int capacity(int expectedSize) { + checkArgument(expectedSize >= 0); + return Math.max(expectedSize * 2, 16); + } + + /** + * Creates a mutable {@code HashMap} instance with the same mappings as + * the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#copyOf(Map)} instead. + * + *

Note: if {@code K} is an {@link Enum} type, use {@link + * #newEnumMap} instead. + * + * @param map the mappings to be placed in the new map + * @return a new {@code HashMap} initialized with the mappings from + * {@code map} + */ + public static HashMap newHashMap( + Map map) { + return new HashMap(map); + } + + /** + * Creates a mutable, empty, insertion-ordered {@code LinkedHashMap} + * instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#of()} instead. + * + * @return a new, empty {@code LinkedHashMap} + */ + public static LinkedHashMap newLinkedHashMap() { + return new LinkedHashMap(); + } + + /** + * Creates a mutable, insertion-ordered {@code LinkedHashMap} instance + * with the same mappings as the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableMap#copyOf(Map)} instead. + * + * @param map the mappings to be placed in the new map + * @return a new, {@code LinkedHashMap} initialized with the + * mappings from {@code map} + */ + public static LinkedHashMap + newLinkedHashMap(Map map) { + return new LinkedHashMap(map); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the natural + * ordering of its elements. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedMap#of()} instead. + * + * @return a new, empty {@code TreeMap} + */ + @SuppressWarnings("unchecked") // eclipse doesn't like the raw Comparable + public static TreeMap newTreeMap() { + return new TreeMap(); + } + + /** + * Creates a mutable {@code TreeMap} instance with the same mappings as + * the specified map and using the same ordering as the specified map. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedMap#copyOfSorted(SortedMap)} instead. + * + * @param map the sorted map whose mappings are to be placed in the new map + * and whose comparator is to be used to sort the new map + * @return a new {@code TreeMap} initialized with the mappings from {@code + * map} and using the comparator of {@code map} + */ + public static TreeMap newTreeMap(SortedMap map) { + return new TreeMap(map); + } + + /** + * Creates a mutable, empty {@code TreeMap} instance using the given + * comparator. + * + *

Note: if mutability is not required, use {@code + * ImmutableSortedMap.orderedBy(comparator).build()} instead. + * + * @param comparator the comparator to sort the keys with + * @return a new, empty {@code TreeMap} + */ + public static TreeMap newTreeMap( + @Nullable Comparator comparator) { + // Ideally, the extra type parameter "C" shouldn't be necessary. It is a + // work-around of a compiler type inference quirk that prevents the + // following code from being compiled: + // Comparator> comparator = null; + // Map, String> map = newTreeMap(comparator); + return new TreeMap(comparator); + } + + /** + * Creates an {@code EnumMap} instance. + * + * @param type the key type for this map + * @return a new, empty {@code EnumMap} + */ + public static , V> EnumMap newEnumMap(Class type) { + return new EnumMap(checkNotNull(type)); + } + + /** + * Creates an {@code EnumMap} with the same mappings as the specified map. + * + * @param map the map from which to initialize this {@code EnumMap} + * @return a new {@code EnumMap} initialized with the mappings from {@code + * map} + * @throws IllegalArgumentException if {@code m} is not an {@code EnumMap} + * instance and contains no mappings + */ + public static , V> EnumMap newEnumMap( + Map map) { + return new EnumMap(map); + } + + /** + * Creates an {@code IdentityHashMap} instance. + * + * @return a new, empty {@code IdentityHashMap} + */ + public static IdentityHashMap newIdentityHashMap() { + return new IdentityHashMap(); + } + + /** + * Returns a synchronized (thread-safe) bimap backed by the specified bimap. + * In order to guarantee serial access, it is critical that all access + * to the backing bimap is accomplished through the returned bimap. + * + *

It is imperative that the user manually synchronize on the returned map + * when accessing any of its collection views:

   {@code
+   *
+   *   BiMap map = Maps.synchronizedBiMap(
+   *       HashBiMap.create());
+   *    ...
+   *   Set set = map.keySet();  // Needn't be in synchronized block
+   *    ...
+   *   synchronized (map) {  // Synchronizing on map, not set!
+   *     Iterator it = set.iterator(); // Must be in synchronized block
+   *     while (it.hasNext()) {
+   *       foo(it.next());
+   *     }
+   *   }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned bimap will be serializable if the specified bimap is + * serializable. + * + * @param bimap the bimap to be wrapped in a synchronized view + * @return a sychronized view of the specified bimap + */ + public static BiMap synchronizedBiMap(BiMap bimap) { + return Synchronized.biMap(bimap, null); + } + + /** + * Computes the difference between two maps. This difference is an immutable + * snapshot of the state of the maps at the time this method is called. It + * will never change, even if the maps change at a later time. + * + *

Since this method uses {@code HashMap} instances internally, the keys of + * the supplied maps must be well-behaved with respect to + * {@link Object#equals} and {@link Object#hashCode}. + * + *

Note:If you only need to know whether two maps have the same + * mappings, call {@code left.equals(right)} instead of this method. + * + * @param left the map to treat as the "left" map for purposes of comparison + * @param right the map to treat as the "right" map for purposes of comparison + * @return the difference between the two maps + */ + public static MapDifference difference( + Map left, Map right) { + Map onlyOnLeft = newHashMap(); + Map onlyOnRight = new HashMap(right); // will whittle it down + Map onBoth = newHashMap(); + Map> differences = newHashMap(); + boolean eq = true; + + for (Entry entry : left.entrySet()) { + K leftKey = entry.getKey(); + V leftValue = entry.getValue(); + if (right.containsKey(leftKey)) { + V rightValue = onlyOnRight.remove(leftKey); + if (Objects.equal(leftValue, rightValue)) { + onBoth.put(leftKey, leftValue); + } else { + eq = false; + differences.put(leftKey, new ValueDifferenceImpl( + leftValue, rightValue)); + } + } else { + eq = false; + onlyOnLeft.put(leftKey, leftValue); + } + } + + boolean areEqual = eq && onlyOnRight.isEmpty(); + return new MapDifferenceImpl( + areEqual, onlyOnLeft, onlyOnRight, onBoth, differences); + } + + private static class MapDifferenceImpl + implements MapDifference { + final boolean areEqual; + final Map onlyOnLeft; + final Map onlyOnRight; + final Map onBoth; + final Map> differences; + + MapDifferenceImpl(boolean areEqual, Map onlyOnLeft, + Map onlyOnRight, Map onBoth, + Map> differences) { + this.areEqual = areEqual; + this.onlyOnLeft = Collections.unmodifiableMap(onlyOnLeft); + this.onlyOnRight = Collections.unmodifiableMap(onlyOnRight); + this.onBoth = Collections.unmodifiableMap(onBoth); + this.differences = Collections.unmodifiableMap(differences); + } + + public boolean areEqual() { + return areEqual; + } + + public Map entriesOnlyOnLeft() { + return onlyOnLeft; + } + + public Map entriesOnlyOnRight() { + return onlyOnRight; + } + + public Map entriesInCommon() { + return onBoth; + } + + public Map> entriesDiffering() { + return differences; + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof MapDifference) { + MapDifference other = (MapDifference) object; + return entriesOnlyOnLeft().equals(other.entriesOnlyOnLeft()) + && entriesOnlyOnRight().equals(other.entriesOnlyOnRight()) + && entriesInCommon().equals(other.entriesInCommon()) + && entriesDiffering().equals(other.entriesDiffering()); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(entriesOnlyOnLeft(), entriesOnlyOnRight(), + entriesInCommon(), entriesDiffering()); + } + + @Override public String toString() { + if (areEqual) { + return "equal"; + } + + StringBuilder result = new StringBuilder("not equal"); + if (!onlyOnLeft.isEmpty()) { + result.append(": only on left=").append(onlyOnLeft); + } + if (!onlyOnRight.isEmpty()) { + result.append(": only on right=").append(onlyOnRight); + } + if (!differences.isEmpty()) { + result.append(": value differences=").append(differences); + } + return result.toString(); + } + } + + static class ValueDifferenceImpl + implements MapDifference.ValueDifference { + + private final V left; + private final V right; + + ValueDifferenceImpl(@Nullable V left, @Nullable V right) { + this.left = left; + this.right = right; + } + + public V leftValue() { + return left; + } + + public V rightValue() { + return right; + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof MapDifference.ValueDifference) { + MapDifference.ValueDifference that = + (MapDifference.ValueDifference) object; + return Objects.equal(this.left, that.leftValue()) + && Objects.equal(this.right, that.rightValue()); + } + return false; + } + + @Override public int hashCode() { + return Objects.hashCode(left, right); + } + + @Override public String toString() { + return "(" + left + ", " + right + ")"; + } + } + + /** + * Returns an immutable map for which the {@link Map#values} are the given + * elements in the given order, and each key is the product of invoking a + * supplied function on its corresponding value. + * + * @param values the values to use when constructing the {@code Map} + * @param keyFunction the function used to produce the key for each value + * @return a map mapping the result of evaluating the function {@code + * keyFunction} on each value in the input collection to that value + * @throws IllegalArgumentException if {@code keyFunction} produces the same + * key for more than one value in the input collection + * @throws NullPointerException if any elements of {@code values} is null, or + * if {@code keyFunction} produces {@code null} for any value + */ + // TODO: consider returning a bimap, whose inverse view does lookups by + // invoking the function. + public static ImmutableMap uniqueIndex( + Iterable values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (V value : values) { + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } + + /** + * Creates an {@code ImmutableMap} from a {@code Properties} + * instance. Properties normally derive from {@code Map}, but + * they typically contain strings, which is awkward. This method lets you get + * a plain-old-{@code Map} out of a {@code Properties}. + * + * @param properties a {@code Properties} object to be converted + * @return an immutable map containing all the entries in + * {@code properties} + * @throws ClassCastException if any key in {@code Properties} is not a + * {@code String} + * @throws NullPointerException if any key or value in {@code Properties} is + * null. + */ + public static ImmutableMap + fromProperties(Properties properties) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for (Enumeration e = properties.propertyNames(); e.hasMoreElements();) { + String key = (String) e.nextElement(); + builder.put(key, properties.getProperty(key)); + } + + return builder.build(); + } + + /** + * Returns an immutable map entry with the specified key and value. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}. + * + *

The returned entry is serializable. + * + * @param key the key to be associated with the returned entry + * @param value the value to be associated with the returned entry + */ + public static Entry immutableEntry( + @Nullable final K key, @Nullable final V value) { + return new ImmutableEntry(key, value); + } + + /** + * Returns an unmodifiable view of the specified set of entries. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}, + * as do any operations that would modify the returned set. + * + * @param entrySet the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + static Set> unmodifiableEntrySet( + final Set> entrySet) { + return new UnmodifiableEntrySet(Collections.unmodifiableSet( + entrySet)); + } + + /** + * Returns an unmodifiable view of the specified map entry. The {@link + * Entry#setValue} operation throws an {@link UnsupportedOperationException}. + * This also has the side-effect of redefining {@code equals} to comply with + * the Entry contract, to avoid a possible nefarious implementation of + * equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + private static Entry unmodifiableEntry(final Entry entry) { + checkNotNull(entry); + return new AbstractMapEntry() { + @Override public K getKey() { + return entry.getKey(); + } + @Override public V getValue() { + return entry.getValue(); + } + }; + } + + /** @see Multimaps#unmodifiableEntries */ + static class UnmodifiableEntries + extends ForwardingCollection> { + private final Collection> entries; + + UnmodifiableEntries(Collection> entries) { + this.entries = entries; + } + + @Override protected Collection> delegate() { + return entries; + } + + @Override public Iterator> iterator() { + final Iterator> delegate = super.iterator(); + return new ForwardingIterator>() { + @Override public Entry next() { + return unmodifiableEntry(super.next()); + } + @Override protected Iterator> delegate() { + return delegate; + } + }; + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + + @Override public boolean contains(Object o) { + return containsEntryImpl(delegate(), o); + } + + @Override public boolean containsAll(Collection c) { + return Collections2.containsAll(this, c); + } + } + + /** @see Maps#unmodifiableEntrySet(Set) */ + static class UnmodifiableEntrySet + extends UnmodifiableEntries + implements Set> { + UnmodifiableEntrySet(Set> entries) { + super(entries); + } + + // See java.util.Collections.UnmodifiableEntrySet for details on attacks. + + @Override public boolean equals(@Nullable Object object) { + return Collections2.setEquals(this, object); + } + + @Override public int hashCode() { + return Sets.hashCodeImpl(this); + } + } + + /** + * Returns an unmodifiable view of the specified bimap. This method allows + * modules to provide users with "read-only" access to internal bimaps. Query + * operations on the returned bimap "read through" to the specified bimap, and + * attemps to modify the returned map, whether direct or via its collection + * views, result in an {@code UnsupportedOperationException}. + * + *

The returned bimap will be serializable if the specified bimap is + * serializable. + * + * @param bimap the bimap for which an unmodifiable view is to be returned + * @return an unmodifiable view of the specified bimap + */ + public static BiMap unmodifiableBiMap( + BiMap bimap) { + return new UnmodifiableBiMap(bimap, null); + } + + /** @see Maps#unmodifiableBiMap(BiMap) */ + private static class UnmodifiableBiMap + extends ForwardingMap implements BiMap, Serializable { + + final Map unmodifiableMap; + final BiMap delegate; + transient BiMap inverse; + transient Set values; + + UnmodifiableBiMap(BiMap delegate, + @Nullable BiMap inverse) { + unmodifiableMap = Collections.unmodifiableMap(delegate); + this.delegate = delegate; + this.inverse = inverse; + } + + @Override protected Map delegate() { + return unmodifiableMap; + } + + public V forcePut(K key, V value) { + throw new UnsupportedOperationException(); + } + + public BiMap inverse() { + BiMap result = inverse; + return (result == null) + ? inverse = new UnmodifiableBiMap(delegate.inverse(), this) + : result; + } + + @Override public Set values() { + Set result = values; + return (result == null) + ? values = Collections.unmodifiableSet(delegate.values()) + : result; + } + + private static final long serialVersionUID = 0; + } + + /** + * Implements {@code Collection.contains} safely for forwarding collections of + * map entries. If {@code o} is an instance of {@code Map.Entry}, it is + * wrapped using {@link #unmodifiableEntry} to protect against a possible + * nefarious equals method. + * + *

Note that {@code c} is the backing (delegate) collection, rather than + * the forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object that might be contained in {@code c} + * @return {@code true} if {@code c} contains {@code o} + */ + static boolean containsEntryImpl(Collection> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.contains(unmodifiableEntry((Entry) o)); + } + + /** + * Implements {@code Collection.remove} safely for forwarding collections of + * map entries. If {@code o} is an instance of {@code Map.Entry}, it is + * wrapped using {@link #unmodifiableEntry} to protect against a possible + * nefarious equals method. + * + *

Note that {@code c} is backing (delegate) collection, rather than the + * forwarding collection. + * + * @param c the delegate (unwrapped) collection of map entries + * @param o the object to remove from {@code c} + * @return {@code true} if {@code c} was changed + */ + static boolean removeEntryImpl(Collection> c, Object o) { + if (!(o instanceof Entry)) { + return false; + } + return c.remove(unmodifiableEntry((Entry) o)); + } + + /** + * Returns a view of a map where each value is transformed by a function. All + * other properties of the map, such as iteration order, are left intact. For + * example, the code: + *

   {@code
+   *
+   *   Map map = ImmutableMap.of("a", 4, "b", 9);
+   *   Function sqrt = new Function() {
+   *     public Double apply(Integer in) {
+   *       return Math.sqrt((int) in);
+   *     }
+   *   };
+   *   Map transformed = Maps.transformValues(sqrt, map);
+   *   System.out.println(transformed);}
+ * + * ... prints {@code {a=2.0, b=3.0}}. + * + *

Changes in the underlying map are reflected in this view. Conversely, + * this view supports removal operations, and these are reflected in the + * underlying map. + * + *

It's acceptable for the underlying map to contain null keys, and even + * null values provided that the function is capable of accepting null input. + * The transformed map might contain null values, if the function sometimes + * gives a null result. + * + *

The returned map is not thread-safe or serializable, even if the + * underlying map is. + * + *

The function is applied lazily, invoked when needed. This is necessary + * for the returned map to be a view, but it means that the function will be + * applied many times for bulk operations like {@link Map#containsValue} and + * {@code Map.toString()}. For this to perform well, {@code function} should + * be fast. To avoid lazy evaluation when the returned map doesn't need to be + * a view, copy the returned map into a new map of your choosing. + */ + public static Map transformValues( + Map fromMap, Function function) { + return new TransformedValuesMap(fromMap, function); + } + + private static class TransformedValuesMap + extends AbstractMap { + final Map fromMap; + final Function function; + + TransformedValuesMap(Map fromMap, Function function) + { + this.fromMap = checkNotNull(fromMap); + this.function = checkNotNull(function); + } + + @Override public int size() { + return fromMap.size(); + } + + @Override public boolean containsKey(Object key) { + return fromMap.containsKey(key); + } + + @Override public V2 get(Object key) { + V1 value = fromMap.get(key); + return (value != null || fromMap.containsKey(key)) + ? function.apply(value) : null; + } + + @Override public V2 remove(Object key) { + return fromMap.containsKey(key) + ? function.apply(fromMap.remove(key)) + : null; + } + + @Override public void clear() { + fromMap.clear(); + } + + EntrySet entrySet; + + @Override public Set> entrySet() { + EntrySet result = entrySet; + if (result == null) { + entrySet = result = new EntrySet(); + } + return result; + } + + class EntrySet extends AbstractSet> { + @Override public int size() { + return TransformedValuesMap.this.size(); + } + + @Override public Iterator> iterator() { + final Iterator> mapIterator + = fromMap.entrySet().iterator(); + + return new Iterator>() { + public boolean hasNext() { + return mapIterator.hasNext(); + } + + public Entry next() { + final Entry entry = mapIterator.next(); + return new AbstractMapEntry() { + @Override public K getKey() { + return entry.getKey(); + } + @Override public V2 getValue() { + return function.apply(entry.getValue()); + } + }; + } + + public void remove() { + mapIterator.remove(); + } + }; + } + + @Override public void clear() { + fromMap.clear(); + } + + @Override public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + Object entryKey = entry.getKey(); + Object entryValue = entry.getValue(); + V2 mapValue = TransformedValuesMap.this.get(entryKey); + if (mapValue != null) { + return mapValue.equals(entryValue); + } + return entryValue == null && containsKey(entryKey); + } + + @Override public boolean remove(Object o) { + if (contains(o)) { + Entry entry = (Entry) o; + Object key = entry.getKey(); + fromMap.remove(key); + return true; + } + return false; + } + } + } + + /** + * Returns a map containing the mappings in {@code unfiltered} whose keys + * satisfy a predicate. The returned map is a live view of {@code unfiltered}; + * changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. The map's {@code + * put()} and {@code putAll()} methods throw an {@link + * IllegalArgumentException} if a key that doesn't satisfy the predicate is + * provided. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings whose keys satisfy the + * filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + */ + public static Map filterKeys( + Map unfiltered, final Predicate keyPredicate) { + checkNotNull(keyPredicate); + Predicate> entryPredicate = new Predicate>() { + public boolean apply(Entry input) { + return keyPredicate.apply(input.getKey()); + } + }; + return (unfiltered instanceof AbstractFilteredMap) + ? filterFiltered((AbstractFilteredMap) unfiltered, entryPredicate) + : new FilteredKeyMap( + checkNotNull(unfiltered), keyPredicate, entryPredicate); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} whose values + * satisfy a predicate. The returned map is a live view of {@code unfiltered}; + * changes to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. The {@link Map#put}, + * {@link Map#putAll}, and {@link Entry#setValue} methods throw an {@link + * IllegalArgumentException} if a value that doesn't satisfy the predicate is + * provided. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings whose values satisfy the + * filter will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + */ + public static Map filterValues( + Map unfiltered, final Predicate valuePredicate) { + checkNotNull(valuePredicate); + Predicate> entryPredicate = new Predicate>() { + public boolean apply(Entry input) { + return valuePredicate.apply(input.getValue()); + } + }; + return filterEntries(unfiltered, entryPredicate); + } + + /** + * Returns a map containing the mappings in {@code unfiltered} that satisfy a + * predicate. The returned map is a live view of {@code unfiltered}; changes + * to one affect the other. + * + *

The resulting map's {@code keySet()}, {@code entrySet()}, and {@code + * values()} views have iterators that don't support {@code remove()}, but all + * other methods are supported by the map and its views. The map's {@code + * put()} and {@code putAll()} methods throw an {@link + * IllegalArgumentException} if a key/value pair that doesn't satisfy the + * predicate is provided. Similarly, the map's entries have a {@link + * Entry#setValue} method that throws an {@link IllegalArgumentException} when + * the existing key and the provided value don't satisfy the predicate. + * + *

When methods such as {@code removeAll()} and {@code clear()} are called + * on the filtered map or its views, only mappings that satisfy the filter + * will be removed from the underlying map. + * + *

The returned map isn't threadsafe or serializable, even if {@code + * unfiltered} is. + * + *

Many of the filtered map's methods, such as {@code size()}, + * iterate across every key/value mapping in the underlying map and determine + * which satisfy the filter. When a live view is not needed, it may be + * faster to copy the filtered map and use the copy. + */ + public static Map filterEntries( + Map unfiltered, Predicate> entryPredicate) { + checkNotNull(entryPredicate); + return (unfiltered instanceof AbstractFilteredMap) + ? filterFiltered((AbstractFilteredMap) unfiltered, entryPredicate) + : new FilteredEntryMap(checkNotNull(unfiltered), entryPredicate); + } + + /** + * Support {@code clear()}, {@code removeAll()}, and {@code retainAll()} when + * filtering a filtered map. + */ + private static Map filterFiltered( + AbstractFilteredMap map, + Predicate> entryPredicate) { + Predicate> predicate + = Predicates.and(map.predicate, entryPredicate); + return new FilteredEntryMap(map.unfiltered, predicate); + } + + private static abstract class AbstractFilteredMap + extends AbstractMap { + + final Map unfiltered; + final Predicate> predicate; + + AbstractFilteredMap(Map unfiltered, + Predicate> predicate) { + this.unfiltered = unfiltered; + this.predicate = predicate; + } + + boolean apply(Object key, V value) { + // This method is called only when the key is in the map, implying that + // key is a K. + @SuppressWarnings("unchecked") + K k = (K) key; + return predicate.apply(Maps.immutableEntry(k, value)); + } + + @Override public V put(K key, V value) { + checkArgument(apply(key, value)); + return unfiltered.put(key, value); + } + + @Override public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + checkArgument(apply(entry.getKey(), entry.getValue())); + } + unfiltered.putAll(map); + } + + @Override public boolean containsKey(Object key) { + return unfiltered.containsKey(key) && apply(key, unfiltered.get(key)); + } + + @Override public V get(Object key) { + V value = unfiltered.get(key); + return ((value != null) && apply(key, value)) ? value : null; + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public V remove(Object key) { + return containsKey(key) ? unfiltered.remove(key) : null; + } + + Collection values; + + @Override public Collection values() { + Collection result = values; + return (result == null) ? values = new Values() : result; + } + + class Values extends AbstractCollection { + @Override public Iterator iterator() { + final Iterator> entryIterator = entrySet().iterator(); + return new UnmodifiableIterator() { + public boolean hasNext() { + return entryIterator.hasNext(); + } + public V next() { + return entryIterator.next().getValue(); + } + }; + } + + @Override public int size() { + return entrySet().size(); + } + + @Override public void clear() { + entrySet().clear(); + } + + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + + @Override public boolean remove(Object o) { + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (Objects.equal(o, entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection collection) { + checkNotNull(collection); + boolean changed = false; + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (collection.contains(entry.getValue()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public boolean retainAll(Collection collection) { + checkNotNull(collection); + boolean changed = false; + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (!collection.contains(entry.getValue()) + && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + } + + private static class FilteredKeyMap extends AbstractFilteredMap { + Predicate keyPredicate; + + FilteredKeyMap(Map unfiltered, Predicate keyPredicate, + Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + this.keyPredicate = keyPredicate; + } + + Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) + ? entrySet = Sets.filter(unfiltered.entrySet(), predicate) + : result; + } + + Set keySet; + + @Override public Set keySet() { + Set result = keySet; + return (result == null) + ? keySet = Sets.filter(unfiltered.keySet(), keyPredicate) + : result; + } + + // The cast is called only when the key is in the unfiltered map, implying + // that key is a K. + @SuppressWarnings("unchecked") + @Override public boolean containsKey(Object key) { + return unfiltered.containsKey(key) && keyPredicate.apply((K) key); + } + } + + private static class FilteredEntryMap + extends AbstractFilteredMap { + /** + * Entries in this set satisfy the predicate, but they don't validate the + * input to {@code Entry.setValue()}. + */ + final Set> filteredEntrySet; + + FilteredEntryMap(Map unfiltered, + Predicate> entryPredicate) { + super(unfiltered, entryPredicate); + filteredEntrySet = Sets.filter(unfiltered.entrySet(), predicate); + } + + Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + return (result == null) ? entrySet = new EntrySet() : result; + } + + private class EntrySet extends ForwardingSet> { + @Override protected Set> delegate() { + return filteredEntrySet; + } + + @Override public Iterator> iterator() { + final Iterator> iterator = filteredEntrySet.iterator(); + return new UnmodifiableIterator>() { + public boolean hasNext() { + return iterator.hasNext(); + } + public Entry next() { + final Entry entry = iterator.next(); + return new ForwardingMapEntry() { + @Override protected Entry delegate() { + return entry; + } + @Override public V setValue(V value) { + checkArgument(apply(entry.getKey(), value)); + return super.setValue(value); + } + }; + } + }; + } + } + + Set keySet; + + @Override public Set keySet() { + Set result = keySet; + return (result == null) ? keySet = new KeySet() : result; + } + + private class KeySet extends AbstractSet { + @Override public Iterator iterator() { + final Iterator> iterator = filteredEntrySet.iterator(); + return new UnmodifiableIterator() { + public boolean hasNext() { + return iterator.hasNext(); + } + public K next() { + return iterator.next().getKey(); + } + }; + } + + @Override public int size() { + return filteredEntrySet.size(); + } + + @Override public void clear() { + filteredEntrySet.clear(); + } + + @Override public boolean contains(Object o) { + return containsKey(o); + } + + @Override public boolean remove(Object o) { + if (containsKey(o)) { + unfiltered.remove(o); + return true; + } + return false; + } + + @Override public boolean removeAll(Collection collection) { + checkNotNull(collection); // for GWT + boolean changed = false; + for (Object obj : collection) { + changed |= remove(obj); + } + return changed; + } + + @Override public boolean retainAll(Collection collection) { + checkNotNull(collection); // for GWT + boolean changed = false; + Iterator> iterator = unfiltered.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + if (!collection.contains(entry.getKey()) && predicate.apply(entry)) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + @Override public Object[] toArray() { + // creating an ArrayList so filtering happens once + return Lists.newArrayList(iterator()).toArray(); + } + + @Override public T[] toArray(T[] array) { + return Lists.newArrayList(iterator()).toArray(array); + } + } + } + + /** + * {@code AbstractMap} extension that implements {@link #isEmpty()} as {@code + * entrySet().isEmpty()} instead of {@code size() == 0} to speed up + * implementations where {@code size()} is O(n), and it delegates the {@code + * isEmpty()} methods of its key set and value collection to this + * implementation. + */ + @GwtCompatible + abstract static class ImprovedAbstractMap + extends AbstractMap { + + /** + * Creates the entry set to be returned by {@link #entrySet()}. This method + * is invoked at most once on a given map, at the time when {@code + * entrySet} is first called. + */ + protected abstract Set> createEntrySet(); + + private transient Set> entrySet; + + @Override public Set> entrySet() { + Set> result = entrySet; + if (result == null) { + entrySet = result = createEntrySet(); + } + return result; + } + + private transient Set keySet; + + @Override public Set keySet() { + Set result = keySet; + if (result == null) { + final Set delegate = super.keySet(); + keySet = result = new ForwardingSet() { + @Override protected Set delegate() { + return delegate; + } + + @Override public boolean isEmpty() { + return ImprovedAbstractMap.this.isEmpty(); + } + }; + } + return result; + } + + private transient Collection values; + + @Override public Collection values() { + Collection result = values; + if (result == null) { + final Collection delegate = super.values(); + values = result = new ForwardingCollection() { + @Override protected Collection delegate() { + return delegate; + } + + @Override public boolean isEmpty() { + return ImprovedAbstractMap.this.isEmpty(); + } + }; + } + return result; + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + *

The implementation returns {@code entrySet().isEmpty()}. + * + * @return {@code true} if this map contains no key-value mappings + */ + @Override public boolean isEmpty() { + return entrySet().isEmpty(); + } + } + + static final MapJoiner standardJoiner + = Collections2.standardJoiner.withKeyValueSeparator("="); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multimap.java new file mode 100644 index 00000000000..e1f60b5f7f3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multimap.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A collection similar to a {@code Map}, but which may associate multiple + * values with a single key. If you call {@link #put} twice, with the same key + * but different values, the multimap contains mappings from the key to both + * values. + * + *

The methods {@link #get}, {@link #keySet}, {@link #keys}, {@link #values}, + * {@link #entries}, and {@link #asMap} return collections that are views of the + * multimap. If the multimap is modifiable, updating it can change the contents + * of those collections, and updating the collections will change the multimap. + * In contrast, {@link #replaceValues} and {@link #removeAll} return collections + * that are independent of subsequent multimap changes. + * + *

Depending on the implementation, a multimap may or may not allow duplicate + * key-value pairs. In other words, the multimap contents after adding the same + * key and value twice varies between implementations. In multimaps allowing + * duplicates, the multimap will contain two mappings, and {@code get} will + * return a collection that includes the value twice. In multimaps not + * supporting duplicates, the multimap will contain a single mapping from the + * key to the value, and {@code get} will return a collection that includes the + * value once. + * + *

All methods that alter the multimap are optional, and the views returned + * by the multimap may or may not be modifiable. When modification isn't + * supported, those methods will throw an {@link UnsupportedOperationException}. + * + * @author Jared Levy + * @param the type of keys maintained by this multimap + * @param the type of mapped values + */ +@GwtCompatible +public interface Multimap { + // Query Operations + + /** Returns the number of key-value pairs in the multimap. */ + int size(); + + /** Returns {@code true} if the multimap contains no key-value pairs. */ + boolean isEmpty(); + + /** + * Returns {@code true} if the multimap contains any values for the specified + * key. + * + * @param key key to search for in multimap + */ + boolean containsKey(@Nullable Object key); + + /** + * Returns {@code true} if the multimap contains the specified value for any + * key. + * + * @param value value to search for in multimap + */ + boolean containsValue(@Nullable Object value); + + /** + * Returns {@code true} if the multimap contains the specified key-value pair. + * + * @param key key to search for in multimap + * @param value value to search for in multimap + */ + boolean containsEntry(@Nullable Object key, @Nullable Object value); + + // Modification Operations + + /** + * Stores a key-value pair in the multimap. + * + *

Some multimap implementations allow duplicate key-value pairs, in which + * case {@code put} always adds a new key-value pair and increases the + * multimap size by 1. Other implementations prohibit duplicates, and storing + * a key-value pair that's already in the multimap has no effect. + * + * @param key key to store in the multimap + * @param value value to store in the multimap + * @return {@code true} if the method increased the size of the multimap, or + * {@code false} if the multimap already contained the key-value pair and + * doesn't allow duplicates + */ + boolean put(@Nullable K key, @Nullable V value); + + /** + * Removes a key-value pair from the multimap. + * + * @param key key of entry to remove from the multimap + * @param value value of entry to remove the multimap + * @return {@code true} if the multimap changed + */ + boolean remove(@Nullable Object key, @Nullable Object value); + + // Bulk Operations + + /** + * Stores a collection of values with the same key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(@Nullable K key, Iterable values); + + /** + * Copies all of another multimap's key-value pairs into this multimap. The + * order in which the mappings are added is determined by + * {@code multimap.entries()}. + * + * @param multimap mappings to store in this multimap + * @return {@code true} if the multimap changed + */ + boolean putAll(Multimap multimap); + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. + * + * @param key key to store in the multimap + * @param values values to store in the multimap + * @return the collection of replaced values, or an empty collection if no + * values were previously associated with the key. The collection + * may be modifiable, but updating it will have no effect on the + * multimap. + */ + Collection replaceValues(@Nullable K key, Iterable values); + + /** + * Removes all values associated with a given key. + * + * @param key key of entries to remove from the multimap + * @return the collection of removed values, or an empty collection if no + * values were associated with the provided key. The collection + * may be modifiable, but updating it will have no effect on the + * multimap. + */ + Collection removeAll(@Nullable Object key); + + /** + * Removes all key-value pairs from the multimap. + */ + void clear(); + + // Views + + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + *

Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + * @param key key to search for in multimap + * @return the collection of values that the key maps to + */ + Collection get(@Nullable K key); + + /** + * Returns the set of all keys, each appearing once in the returned set. + * Changes to the returned set will update the underlying multimap, and vice + * versa. + * + * @return the collection of distinct keys + */ + Set keySet(); + + /** + * Returns a collection, which may contain duplicates, of all keys. The number + * of times of key appears in the returned multiset equals the number of + * mappings the key has in the multimap. Changes to the returned multiset will + * update the underlying multimap, and vice versa. + * + * @return a multiset with keys corresponding to the distinct keys of the + * multimap and frequencies corresponding to the number of values that + * each key maps to + */ + Multiset keys(); + + /** + * Returns a collection of all values in the multimap. Changes to the returned + * collection will update the underlying multimap, and vice versa. + * + * @return collection of values, which may include the same value multiple + * times if it occurs in multiple mappings + */ + Collection values(); + + /** + * Returns a collection of all key-value pairs. Changes to the returned + * collection will update the underlying multimap, and vice versa. The entries + * collection does not support the {@code add} or {@code addAll} operations. + * + * @return collection of map entries consisting of key-value pairs + */ + Collection> entries(); + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, + * will update the underlying multimap. The map does not support + * {@code setValue()} on its entries, {@code put}, or {@code putAll}. + * + *

The collections returned by {@code asMap().get(Object)} have the same + * behavior as those returned by {@link #get}. + * + * @return a map view from a key to its collection of values + */ + Map> asMap(); + + // Comparison and hashing + + /** + * Compares the specified object with this multimap for equality. Two + * multimaps are equal when their map views, as returned by {@link #asMap}, + * are also equal. + * + *

In general, two multimaps with identical key-value mappings may or may + * not be equal, depending on the implementation. For example, two + * {@link SetMultimap} instances with the same key-value mappings are equal, + * but equality of two {@link ListMultimap} instances depends on the ordering + * of the values for each key. + * + *

A non-empty {@link SetMultimap} cannot be equal to a non-empty + * {@link ListMultimap}, since their {@link #asMap} views contain unequal + * collections as values. However, any two empty multimaps are equal, because + * they both have empty {@link #asMap} views. + */ + boolean equals(@Nullable Object obj); + + /** + * Returns the hash code for this multimap. + * + *

The hash code of a multimap is defined as the hash code of the map view, + * as returned by {@link Multimap#asMap}. + */ + int hashCode(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multimaps.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multimaps.java new file mode 100644 index 00000000000..4908d97b317 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multimaps.java @@ -0,0 +1,1227 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.base.Joiner; +import org.elasticsearch.util.gcommon.base.Joiner.MapJoiner; +import org.elasticsearch.util.gcommon.base.Preconditions; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkState; +import org.elasticsearch.util.gcommon.base.Supplier; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Provides static methods acting on or generating a {@code Multimap}. + * + * @author Jared Levy + * @author Robert Konigsberg + * @author Mike Bostock + */ +@GwtCompatible +public final class Multimaps { + private Multimaps() {} + + /** + * Creates a new {@code Multimap} that uses the provided map and factory. It + * can generate a multimap based on arbitrary {@link Map} and + * {@link Collection} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * collections generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedMultimap}. + * + *

Call this method only when the simpler methods + * {@link ArrayListMultimap#create()}, {@link HashMultimap#create()}, + * {@link LinkedHashMultimap#create()}, {@link LinkedListMultimap#create()}, + * {@link TreeMultimap#create()}, and + * {@link TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the collections returned by {@code factory}. Those objects should not be + * manually updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty collections that will each hold all + * values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static Multimap newMultimap(Map> map, + final Supplier> factory) { + return new CustomMultimap(map, factory); + } + + private static class CustomMultimap extends AbstractMultimap { + transient Supplier> factory; + + CustomMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected Collection createCollection() { + return factory.get(); + } + + // can't use Serialization writeMultimap and populateMultimap methods since + // there's no way to generate the empty backing map. + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code ListMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link List} + * classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. The multimap's {@code get}, {@code + * removeAll}, and {@code replaceValues} methods return {@code RandomAccess} + * lists if the factory does. However, the multimap's {@code get} method + * returns instances of a different class than does {@code factory.get()}. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * lists generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedListMultimap}. + * + *

Call this method only when the simpler methods + * {@link ArrayListMultimap#create()} and {@link LinkedListMultimap#create()} + * won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the lists returned by {@code factory}. Those objects should not be manually + * updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty lists that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static ListMultimap newListMultimap( + Map> map, final Supplier> factory) { + return new CustomListMultimap(map, factory); + } + + private static class CustomListMultimap + extends AbstractListMultimap { + transient Supplier> factory; + + CustomListMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected List createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SetMultimap} that uses the provided map and factory. + * It can generate a multimap based on arbitrary {@link Map} and {@link Set} + * classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * sets generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSetMultimap}. + * + *

Call this method only when the simpler methods + * {@link HashMultimap#create()}, {@link LinkedHashMultimap#create()}, + * {@link TreeMultimap#create()}, and + * {@link TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the sets returned by {@code factory}. Those objects should not be manually + * updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty sets that will each hold all values + * for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SetMultimap newSetMultimap( + Map> map, final Supplier> factory) { + return new CustomSetMultimap(map, factory); + } + + private static class CustomSetMultimap + extends AbstractSetMultimap { + transient Supplier> factory; + + CustomSetMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + } + + @Override protected Set createCollection() { + return factory.get(); + } + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Creates a new {@code SortedSetMultimap} that uses the provided map and + * factory. It can generate a multimap based on arbitrary {@link Map} and + * {@link SortedSet} classes. + * + *

The {@code factory}-generated and {@code map} classes determine the + * multimap iteration order. They also specify the behavior of the + * {@code equals}, {@code hashCode}, and {@code toString} methods for the + * multimap and its returned views. However, the multimap's {@code get} + * method returns instances of a different class than {@code factory.get()} + * does. + * + *

The multimap is serializable if {@code map}, {@code factory}, the + * sets generated by {@code factory}, and the multimap contents are all + * serializable. + * + *

The multimap is not threadsafe when any concurrent operations update the + * multimap, even if {@code map} and the instances generated by + * {@code factory} are. Concurrent read operations will work correctly. To + * allow concurrent update operations, wrap the multimap with a call to + * {@link #synchronizedSortedSetMultimap}. + * + *

Call this method only when the simpler methods + * {@link TreeMultimap#create()} and + * {@link TreeMultimap#create(Comparator, Comparator)} won't suffice. + * + *

Note: the multimap assumes complete ownership over of {@code map} and + * the sets returned by {@code factory}. Those objects should not be manually + * updated and they should not use soft, weak, or phantom references. + * + * @param map place to store the mapping from each key to its corresponding + * values + * @param factory supplier of new, empty sorted sets that will each hold + * all values for a given key + * @throws IllegalArgumentException if {@code map} is not empty + */ + public static SortedSetMultimap newSortedSetMultimap( + Map> map, + final Supplier> factory) { + return new CustomSortedSetMultimap(map, factory); + } + + private static class CustomSortedSetMultimap + extends AbstractSortedSetMultimap { + transient Supplier> factory; + transient Comparator valueComparator; + + CustomSortedSetMultimap(Map> map, + Supplier> factory) { + super(map); + this.factory = checkNotNull(factory); + valueComparator = factory.get().comparator(); + } + + @Override protected SortedSet createCollection() { + return factory.get(); + } + + /*@Override*/ public Comparator valueComparator() { + return valueComparator; + } + + /** @serialData the factory and the backing map */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(factory); + stream.writeObject(backingMap()); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + factory = (Supplier>) stream.readObject(); + valueComparator = factory.get().comparator(); + Map> map = (Map>) stream.readObject(); + setMap(map); + } + + private static final long serialVersionUID = 0; + } + + /** + * Copies each key-value mapping in {@code source} into {@code dest}, with + * its key and value reversed. + * + * @param source any multimap + * @param dest the multimap to copy into; usually empty + * @return {@code dest} + */ + public static > M invertFrom( + Multimap source, M dest) { + for (Map.Entry entry : source.entries()) { + dest.put(entry.getValue(), entry.getKey()); + } + return dest; + } + + /** + * Returns a synchronized (thread-safe) multimap backed by the specified + * multimap. In order to guarantee serial access, it is critical that + * all access to the backing multimap is accomplished through the + * returned multimap. + * + *

It is imperative that the user manually synchronize on the returned + * multimap when accessing any of its collection views:

  {@code
+   *
+   *  Multimap m = Multimaps.synchronizedMultimap(
+   *      HashMultimap.create());
+   *  ...
+   *  Set s = m.keySet();  // Needn't be in synchronized block
+   *  ...
+   *  synchronized (m) {  // Synchronizing on m, not s!
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that aren't + * synchronized. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static Multimap synchronizedMultimap( + Multimap multimap) { + return Synchronized.multimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified multimap. Query operations on + * the returned multimap "read through" to the specified multimap, and + * attempts to modify the returned multimap, either directly or through the + * multimap's views, result in an {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static Multimap unmodifiableMultimap( + Multimap delegate) { + return new UnmodifiableMultimap(delegate); + } + + private static class UnmodifiableMultimap + extends ForwardingMultimap implements Serializable { + final Multimap delegate; + transient Collection> entries; + transient Multiset keys; + transient Set keySet; + transient Collection values; + transient Map> map; + + UnmodifiableMultimap(final Multimap delegate) { + this.delegate = delegate; + } + + @Override protected Multimap delegate() { + return delegate; + } + + @Override public void clear() { + throw new UnsupportedOperationException(); + } + + @Override public Map> asMap() { + Map> result = map; + if (result == null) { + final Map> unmodifiableMap + = Collections.unmodifiableMap(delegate.asMap()); + map = result = new ForwardingMap>() { + @Override protected Map> delegate() { + return unmodifiableMap; + } + + Set>> entrySet; + + @Override public Set>> entrySet() { + Set>> result = entrySet; + return (result == null) + ? entrySet + = unmodifiableAsMapEntries(unmodifiableMap.entrySet()) + : result; + } + + @Override public Collection get(Object key) { + Collection collection = unmodifiableMap.get(key); + return (collection == null) + ? null : unmodifiableValueCollection(collection); + } + + Collection> asMapValues; + + @Override public Collection> values() { + Collection> result = asMapValues; + return (result == null) + ? asMapValues + = new UnmodifiableAsMapValues(unmodifiableMap.values()) + : result; + } + + @Override public boolean containsValue(Object o) { + return values().contains(o); + } + }; + } + return result; + } + + @Override public Collection> entries() { + Collection> result = entries; + if (result == null) { + entries = result = unmodifiableEntries(delegate.entries()); + } + return result; + } + + @Override public Collection get(K key) { + return unmodifiableValueCollection(delegate.get(key)); + } + + @Override public Multiset keys() { + Multiset result = keys; + if (result == null) { + keys = result = Multisets.unmodifiableMultiset(delegate.keys()); + } + return result; + } + + @Override public Set keySet() { + Set result = keySet; + if (result == null) { + keySet = result = Collections.unmodifiableSet(delegate.keySet()); + } + return result; + } + + @Override public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean putAll(K key, + @SuppressWarnings("hiding") Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override public Collection removeAll(Object key) { + throw new UnsupportedOperationException(); + } + + @Override public Collection replaceValues(K key, + @SuppressWarnings("hiding") Iterable values) { + throw new UnsupportedOperationException(); + } + + @Override public Collection values() { + Collection result = values; + if (result == null) { + values = result = Collections.unmodifiableCollection(delegate.values()); + } + return result; + } + + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableAsMapValues + extends ForwardingCollection> { + final Collection> delegate; + UnmodifiableAsMapValues(Collection> delegate) { + this.delegate = Collections.unmodifiableCollection(delegate); + } + @Override protected Collection> delegate() { + return delegate; + } + @Override public Iterator> iterator() { + final Iterator> iterator = delegate.iterator(); + return new Iterator>() { + public boolean hasNext() { + return iterator.hasNext(); + } + public Collection next() { + return unmodifiableValueCollection(iterator.next()); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + @Override public boolean contains(Object o) { + return Iterators.contains(iterator(), o); + } + @Override public boolean containsAll(Collection c) { + return Collections2.containsAll(this, c); + } + } + + private static class UnmodifiableListMultimap + extends UnmodifiableMultimap implements ListMultimap { + UnmodifiableListMultimap(ListMultimap delegate) { + super(delegate); + } + @Override public ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + @Override public List get(K key) { + return Collections.unmodifiableList(delegate().get(key)); + } + @Override public List removeAll(Object key) { + throw new UnsupportedOperationException(); + } + @Override public List replaceValues( + K key, @SuppressWarnings("hiding") Iterable values) { + throw new UnsupportedOperationException(); + } + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSetMultimap + extends UnmodifiableMultimap implements SetMultimap { + UnmodifiableSetMultimap(SetMultimap delegate) { + super(delegate); + } + @Override public SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + @Override public Set get(K key) { + /* + * Note that this doesn't return a SortedSet when delegate is a + * SortedSetMultiset, unlike (SortedSet) super.get(). + */ + return Collections.unmodifiableSet(delegate().get(key)); + } + @Override public Set> entries() { + return Maps.unmodifiableEntrySet(delegate().entries()); + } + @Override public Set removeAll(Object key) { + throw new UnsupportedOperationException(); + } + @Override public Set replaceValues( + K key, @SuppressWarnings("hiding") Iterable values) { + throw new UnsupportedOperationException(); + } + private static final long serialVersionUID = 0; + } + + private static class UnmodifiableSortedSetMultimap + extends UnmodifiableSetMultimap implements SortedSetMultimap { + UnmodifiableSortedSetMultimap(SortedSetMultimap delegate) { + super(delegate); + } + @Override public SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + @Override public SortedSet get(K key) { + return Collections.unmodifiableSortedSet(delegate().get(key)); + } + @Override public SortedSet removeAll(Object key) { + throw new UnsupportedOperationException(); + } + @Override public SortedSet replaceValues( + K key, @SuppressWarnings("hiding") Iterable values) { + throw new UnsupportedOperationException(); + } + public Comparator valueComparator() { + return delegate().valueComparator(); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) {@code SetMultimap} backed by the + * specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static SetMultimap synchronizedSetMultimap( + SetMultimap multimap) { + return Synchronized.setMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SetMultimap}. Query + * operations on the returned multimap "read through" to the specified + * multimap, and attempts to modify the returned multimap, either directly or + * through the multimap's views, result in an + * {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static SetMultimap unmodifiableSetMultimap( + SetMultimap delegate) { + return new UnmodifiableSetMultimap(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code SortedSetMultimap} backed by + * the specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static SortedSetMultimap + synchronizedSortedSetMultimap(SortedSetMultimap multimap) { + return Synchronized.sortedSetMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code SortedSetMultimap}. + * Query operations on the returned multimap "read through" to the specified + * multimap, and attempts to modify the returned multimap, either directly or + * through the multimap's views, result in an + * {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static SortedSetMultimap unmodifiableSortedSetMultimap( + SortedSetMultimap delegate) { + return new UnmodifiableSortedSetMultimap(delegate); + } + + /** + * Returns a synchronized (thread-safe) {@code ListMultimap} backed by the + * specified multimap. + * + *

You must follow the warnings described in {@link #synchronizedMultimap}. + * + * @param multimap the multimap to be wrapped + * @return a synchronized view of the specified multimap + */ + public static ListMultimap synchronizedListMultimap( + ListMultimap multimap) { + return Synchronized.listMultimap(multimap, null); + } + + /** + * Returns an unmodifiable view of the specified {@code ListMultimap}. Query + * operations on the returned multimap "read through" to the specified + * multimap, and attempts to modify the returned multimap, either directly or + * through the multimap's views, result in an + * {@code UnsupportedOperationException}. + * + *

Note that the generated multimap's {@link Multimap#removeAll} and + * {@link Multimap#replaceValues} methods return collections that are + * modifiable. + * + *

The returned multimap will be serializable if the specified multimap is + * serializable. + * + * @param delegate the multimap for which an unmodifiable view is to be + * returned + * @return an unmodifiable view of the specified multimap + */ + public static ListMultimap unmodifiableListMultimap( + ListMultimap delegate) { + return new UnmodifiableListMultimap(delegate); + } + + /** + * Returns an unmodifiable view of the specified collection, preserving the + * interface for instances of {@code SortedSet}, {@code Set}, {@code List} and + * {@code Collection}, in that order of preference. + * + * @param collection the collection for which to return an unmodifiable view + * @return an unmodifiable view of the collection + */ + private static Collection unmodifiableValueCollection( + Collection collection) { + if (collection instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (collection instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else if (collection instanceof List) { + return Collections.unmodifiableList((List) collection); + } + return Collections.unmodifiableCollection(collection); + } + + /** + * Returns an unmodifiable view of the specified multimap {@code asMap} entry. + * The {@link Entry#setValue} operation throws an {@link + * UnsupportedOperationException}, and the collection returned by {@code + * getValue} is also an unmodifiable (type-preserving) view. This also has the + * side-effect of redefining equals to comply with the Map.Entry contract, and + * to avoid a possible nefarious implementation of equals. + * + * @param entry the entry for which to return an unmodifiable view + * @return an unmodifiable view of the entry + */ + private static Map.Entry> unmodifiableAsMapEntry( + final Map.Entry> entry) { + checkNotNull(entry); + return new AbstractMapEntry>() { + @Override public K getKey() { + return entry.getKey(); + } + + @Override public Collection getValue() { + return unmodifiableValueCollection(entry.getValue()); + } + }; + } + + /** + * Returns an unmodifiable view of the specified collection of entries. The + * {@link Entry#setValue} operation throws an {@link + * UnsupportedOperationException}. If the specified collection is a {@code + * Set}, the returned collection is also a {@code Set}. + * + * @param entries the entries for which to return an unmodifiable view + * @return an unmodifiable view of the entries + */ + private static Collection> unmodifiableEntries( + Collection> entries) { + if (entries instanceof Set) { + return Maps.unmodifiableEntrySet((Set>) entries); + } + return new Maps.UnmodifiableEntries( + Collections.unmodifiableCollection(entries)); + } + + /** + * Returns an unmodifiable view of the specified set of {@code asMap} entries. + * The {@link Entry#setValue} operation throws an {@link + * UnsupportedOperationException}, as do any operations that attempt to modify + * the returned collection. + * + * @param asMapEntries the {@code asMap} entries for which to return an + * unmodifiable view + * @return an unmodifiable view of the collection entries + */ + private static Set>> unmodifiableAsMapEntries( + Set>> asMapEntries) { + return new UnmodifiableAsMapEntries( + Collections.unmodifiableSet(asMapEntries)); + } + + /** @see Multimaps#unmodifiableAsMapEntries */ + static class UnmodifiableAsMapEntries + extends ForwardingSet>> { + private final Set>> delegate; + UnmodifiableAsMapEntries(Set>> delegate) { + this.delegate = delegate; + } + + @Override protected Set>> delegate() { + return delegate; + } + + @Override public Iterator>> iterator() { + final Iterator>> iterator = delegate.iterator(); + return new ForwardingIterator>>() { + @Override protected Iterator>> delegate() { + return iterator; + } + @Override public Entry> next() { + return unmodifiableAsMapEntry(iterator.next()); + } + }; + } + + @Override public Object[] toArray() { + return ObjectArrays.toArrayImpl(this); + } + + @Override public T[] toArray(T[] array) { + return ObjectArrays.toArrayImpl(this, array); + } + + @Override public boolean contains(Object o) { + return Maps.containsEntryImpl(delegate(), o); + } + + @Override public boolean containsAll(Collection c) { + return Collections2.containsAll(this, c); + } + + @Override public boolean equals(@Nullable Object object) { + return Collections2.setEquals(this, object); + } + } + + /** + * Returns a multimap view of the specified map. The multimap is backed by the + * map, so changes to the map are reflected in the multimap, and vice versa. + * If the map is modified while an iteration over one of the multimap's + * collection views is in progress (except through the iterator's own {@code + * remove} operation, or through the {@code setValue} operation on a map entry + * returned by the iterator), the results of the iteration are undefined. + * + *

The multimap supports mapping removal, which removes the corresponding + * mapping from the map. It does not support any operations which might add + * mappings, such as {@code put}, {@code putAll} or {@code replaceValues}. + * + *

The returned multimap will be serializable if the specified map is + * serializable. + * + * @param map the backing map for the returned multimap view + */ + public static SetMultimap forMap(Map map) { + return new MapMultimap(map); + } + + /** @see Multimaps#forMap */ + private static class MapMultimap + implements SetMultimap, Serializable { + final Map map; + transient Map> asMap; + + MapMultimap(Map map) { + this.map = checkNotNull(map); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public boolean containsEntry(Object key, Object value) { + return map.entrySet().contains(Maps.immutableEntry(key, value)); + } + + public Set get(final K key) { + return new AbstractSet() { + @Override public Iterator iterator() { + return new Iterator() { + int i; + + public boolean hasNext() { + return (i == 0) && map.containsKey(key); + } + + public V next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + i++; + return map.get(key); + } + + public void remove() { + checkState(i == 1); + i = -1; + map.remove(key); + } + }; + } + + @Override public int size() { + return map.containsKey(key) ? 1 : 0; + } + }; + } + + public boolean put(K key, V value) { + throw new UnsupportedOperationException(); + } + + public boolean putAll(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + public boolean putAll(Multimap multimap) { + throw new UnsupportedOperationException(); + } + + public Set replaceValues(K key, Iterable values) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object key, Object value) { + return map.entrySet().remove(Maps.immutableEntry(key, value)); + } + + public Set removeAll(Object key) { + Set values = new HashSet(2); + if (!map.containsKey(key)) { + return values; + } + values.add(map.remove(key)); + return values; + } + + public void clear() { + map.clear(); + } + + public Set keySet() { + return map.keySet(); + } + + public Multiset keys() { + return Multisets.forSet(map.keySet()); + } + + public Collection values() { + return map.values(); + } + + public Set> entries() { + return map.entrySet(); + } + + public Map> asMap() { + Map> result = asMap; + if (result == null) { + asMap = result = new AsMap(); + } + return result; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Multimap) { + Multimap that = (Multimap) object; + return this.size() == that.size() && asMap().equals(that.asMap()); + } + return false; + } + + @Override public int hashCode() { + return map.hashCode(); + } + + private static final MapJoiner joiner + = Joiner.on("], ").withKeyValueSeparator("=[").useForNull("null"); + + @Override public String toString() { + if (map.isEmpty()) { + return "{}"; + } + StringBuilder builder = new StringBuilder(map.size() * 16).append('{'); + joiner.appendTo(builder, map); + return builder.append("]}").toString(); + } + + /** @see MapMultimap#asMap */ + class AsMapEntries extends AbstractSet>> { + @Override public int size() { + return map.size(); + } + + @Override public Iterator>> iterator() { + return new Iterator>>() { + final Iterator keys = map.keySet().iterator(); + + public boolean hasNext() { + return keys.hasNext(); + } + public Entry> next() { + final K key = keys.next(); + return new AbstractMapEntry>() { + @Override public K getKey() { + return key; + } + @Override public Collection getValue() { + return get(key); + } + }; + } + public void remove() { + keys.remove(); + } + }; + } + + @Override public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + if (!(entry.getValue() instanceof Set)) { + return false; + } + Set set = (Set) entry.getValue(); + return (set.size() == 1) + && containsEntry(entry.getKey(), set.iterator().next()); + } + + @Override public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + if (!(entry.getValue() instanceof Set)) { + return false; + } + Set set = (Set) entry.getValue(); + return (set.size() == 1) + && map.entrySet().remove( + Maps.immutableEntry(entry.getKey(), set.iterator().next())); + } + } + + /** @see MapMultimap#asMap */ + class AsMap extends Maps.ImprovedAbstractMap> { + @Override protected Set>> createEntrySet() { + return new AsMapEntries(); + } + + // The following methods are included for performance. + + @Override public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @SuppressWarnings("unchecked") + @Override public Collection get(Object key) { + Collection collection = MapMultimap.this.get((K) key); + return collection.isEmpty() ? null : collection; + } + + @Override public Collection remove(Object key) { + Collection collection = removeAll(key); + return collection.isEmpty() ? null : collection; + } + } + private static final long serialVersionUID = 7845222491160860175L; + } + + /** + * Creates an index {@code ImmutableMultimap} that contains the results of + * applying a specified function to each item in an {@code Iterable} of + * values. Each value will be stored as a value in the resulting multimap, + * yielding a multimap with the same size as the input iterable. The key used + * to store that value in the multimap will be the result of calling the + * function on that value. The resulting multimap is created as an immutable + * snapshot, it does not reflect subsequent changes on the input + * iterable. + * + *

For example,

  {@code
+   *
+   *  List badGuys
+   *      = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
+   *  Function stringLengthFunction = ...;
+   *  Multimap index
+   *      = Multimaps.index(badGuys, stringLengthFunction);
+   *  System.out.println(index);}
+ * + * prints
  {@code
+   *
+   *  {4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}}
+ * + *

The returned multimap is serializable if its keys and values are all + * serializable. + * + * @param values the values to use when constructing the {@code + * ImmutableMultimap} + * @param keyFunction the function used to produce the key for each value + * @return {@code ImmutableMultimap} mapping the result of evaluating the + * function {@code keyFunction} on each value in the input collection to + * that value + * @throws NullPointerException if any of the following cases is true:

    + *
  • {@code values} is null + *
  • {@code keyFunction} is null + *
  • An element in {@code values} is null + *
  • {@code keyFunction} returns null for any element of {@code values} + *
+ */ + public static ImmutableListMultimap index( + Iterable values, Function keyFunction) { + checkNotNull(keyFunction); + ImmutableListMultimap.Builder builder + = ImmutableListMultimap.builder(); + for (V value : values) { + Preconditions.checkNotNull(value, values); + builder.put(keyFunction.apply(value), value); + } + return builder.build(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multiset.java new file mode 100644 index 00000000000..7bb181dfde9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multiset.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A collection that supports order-independent equality, like {@link Set}, but + * may have duplicate elements. A multiset is also sometimes called a + * bag. + * + *

Elements of a multiset that are equal to one another (see "Note on + * element equivalence", below) are referred to as occurrences of the + * same single element. The total number of occurrences of an element in a + * multiset is called the count of that element (the terms "frequency" + * and "multiplicity" are equivalent, but not used in this API). Since the count + * of an element is represented as an {@code int}, a multiset may never contain + * more than {@link Integer#MAX_VALUE} occurrences of any one element. + * + *

{@code Multiset} refines the specifications of several methods from + * {@code Collection}. It also defines an additional query operation, {@link + * #count}, which returns the count of an element. There are five new + * bulk-modification operations, for example {@link #add(Object, int)}, to add + * or remove multiple occurrences of an element at once, or to set the count of + * an element to a specific value. These modification operations are optional, + * but implementations which support the standard collection operations {@link + * #add(Object)} or {@link #remove(Object)} are encouraged to implement the + * related methods as well. Finally, two collection views are provided: {@link + * #elementSet} contains the distinct elements of the multiset "with duplicates + * collapsed", and {@link #entrySet} is similar but contains {@link Entry + * Multiset.Entry} instances, each providing both a distinct element and the + * count of that element. + * + *

In addition to these required methods, implementations of {@code + * Multiset} are expected to provide two {@code static} creation methods: + * {@code create()}, returning an empty multiset, and {@code + * create(Iterable)}, returning a multiset containing the + * given initial elements. This is simply a refinement of {@code Collection}'s + * constructor recommendations, reflecting the new developments of Java 5. + * + *

As with other collection types, the modification operations are optional, + * and should throw {@link UnsupportedOperationException} when they are not + * implemented. Most implementations should support either all add operations + * or none of them, all removal operations or none of them, and if and only if + * all of these are supported, the {@code setCount} methods as well. + * + *

A multiset uses {@link Object#equals} to determine whether two instances + * should be considered "the same," unless specified otherwise by the + * implementation. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public interface Multiset extends Collection { + // Query Operations + + /** + * Returns the number of occurrences of an element in this multiset (the + * count of the element). Note that for an {@link Object#equals}-based + * multiset, this gives the same result as {@link Collections#frequency} + * (which would presumably perform more poorly). + * + *

Note: the utility method {@link Iterables#frequency} generalizes + * this operation; it correctly delegates to this method when dealing with a + * multiset, but it can also accept any other iterable type. + * + * @param element the element to count occurrences of + * @return the number of occurrences of the element in this multiset; possibly + * zero but never negative + */ + int count(@Nullable Object element); + + // Bulk Operations + + /** + * Adds a number of occurrences of an element to this multiset. Note that if + * {@code occurrences == 1}, this method has the identical effect to {@link + * #add(Object)}. This method is functionally equivalent (except in the case + * of overflow) to the call {@code addAll(Collections.nCopies(element, + * occurrences))}, which would presumably perform much more poorly. + * + * @param element the element to add occurrences of; may be {@code null} only + * if explicitly allowed by the implementation + * @param occurrences the number of occurrences of the element to add. May be + * zero, in which case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative, or if + * this operation would result in more than {@link Integer#MAX_VALUE} + * occurrences of the element + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements. Note that if {@code + * occurrences} is zero, the implementation may opt to return normally. + */ + int add(@Nullable E element, int occurrences); + + /** + * Removes a number of occurrences of the specified element from this + * multiset. If the multiset contains fewer than this number of occurrences to + * begin with, all occurrences will be removed. Note that if + * {@code occurrences == 1}, this is functionally equivalent to the call + * {@code remove(element)}. + * + * @param element the element to conditionally remove occurrences of + * @param occurrences the number of occurrences of the element to remove. May + * be zero, in which case no change will be made. + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code occurrences} is negative + */ + int remove(@Nullable Object element, int occurrences); + + /** + * Adds or removes the necessary occurrences of an element such that the + * element attains the desired count. + * + * @param element the element to add or remove occurrences of; may be null + * only if explicitly allowed by the implementation + * @param count the desired count of the element in this multiset + * @return the count of the element before the operation; possibly zero + * @throws IllegalArgumentException if {@code count} is negative + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements. Note that if {@code + * count} is zero, the implementor may optionally return zero instead. + */ + int setCount(E element, int count); + + /** + * Conditionally sets the count of an element to a new value, as described in + * {@link #setCount(Object, int)}, provided that the element has the expected + * current count. If the current count is not {@code oldCount}, no change is + * made. + * + * @param element the element to conditionally set the count of; may be null + * only if explicitly allowed by the implementation + * @param oldCount the expected present count of the element in this multiset + * @param newCount the desired count of the element in this multiset + * @return {@code true} if the condition for modification was met. This + * implies that the multiset was indeed modified, unless + * {@code oldCount == newCount}. + * @throws IllegalArgumentException if {@code oldCount} or {@code newCount} is + * negative + * @throws NullPointerException if {@code element} is null and the + * implementation does not permit null elements. Note that if {@code + * oldCount} and {@code newCount} are both zero, the implementor may + * optionally return {@code true} instead. + */ + boolean setCount(E element, int oldCount, int newCount); + + // Views + + /** + * Returns the set of distinct elements contained in this multiset. The + * element set is backed by the same data as the multiset, so any change to + * either is immediately reflected in the other. The order of the elements in + * the element set is unspecified. + * + *

If the element set supports any removal operations, these necessarily + * cause all occurrences of the removed element(s) to be removed from + * the multiset. Implementations are not expected to support the add + * operations, although this is possible. + * + *

A common use for the element set is to find the number of distinct + * elements in the multiset: {@code elementSet().size()}. + * + * @return a view of the set of distinct elements in this multiset + */ + Set elementSet(); + + /** + * Returns a view of the contents of this multiset, grouped into {@code + * Multiset.Entry} instances, each providing an element of the multiset and + * the count of that element. This set contains exactly one entry for each + * distinct element in the multiset (thus it always has the same size as the + * {@link #elementSet}). The order of the elements in the element set is + * unspecified. + * + *

The entry set is backed by the same data as the multiset, so any change + * to either is immediately reflected in the other. However, multiset changes + * may or may not be reflected in any {@code Entry} instances already + * retrieved from the entry set (this is implementation-dependent). + * Furthermore, implementations are not required to support modifications to + * the entry set at all, and the {@code Entry} instances themselves don't + * even have methods for modification. See the specific implementation class + * for more details on how its entry set handles modifications. + * + * @return a set of entries representing the data of this multiset + */ + Set> entrySet(); + + /** + * An unmodifiable element-count pair for a multiset. The {@link + * Multiset#entrySet} method returns a view of the multiset whose elements + * are of this class. A multiset implementation may return Entry instances + * that are either live "read-through" views to the Multiset, or immutable + * snapshots. Note that this type is unrelated to the similarly-named type + * {@code Map.Entry}. + */ + interface Entry { + + /** + * Returns the multiset element corresponding to this entry. Multiple calls + * to this method always return the same instance. + * + * @return the element corresponding to this entry + */ + E getElement(); + + /** + * Returns the count of the associated element in the underlying multiset. + * This count may either be an unchanging snapshot of the count at the time + * the entry was retrieved, or a live view of the current count of the + * element in the multiset, depending on the implementation. Note that in + * the former case, this method can never return zero, while in the latter, + * it will return zero if all occurrences of the element were since removed + * from the multiset. + * + * @return the count of the element; never negative + */ + int getCount(); + + /** + * {@inheritDoc} + * + *

Returns {@code true} if the given object is also a multiset entry and + * the two entries represent the same element and count. More formally, two + * entries {@code a} and {@code b} are equal if: + * + *

  ((a.getElement() == null)
+     *      ? (b.getElement() == null) : a.getElement().equals(b.getElement()))
+     *    && (a.getCount() == b.getCount())
+ */ + // TODO: check this wrt TreeMultiset? + boolean equals(Object o); + + /** + * {@inheritDoc} + * + *

The hash code of a multiset entry for element {@code element} and + * count {@code count} is defined as: + * + *

  (element == null ? 0 : element.hashCode()) ^ count
+ */ + int hashCode(); + + /** + * Returns the canonical string representation of this entry, defined as + * follows. If the count for this entry is one, this is simply the string + * representation of the corresponding element. Otherwise, it is the string + * representation of the element, followed by the three characters {@code + * " x "} (space, letter x, space), followed by the count. + */ + String toString(); + } + + // Comparison and hashing + + /** + * Compares the specified object with this multiset for equality. Returns + * {@code true} if the given object is also a multiset and contains equal + * elements with equal counts, regardless of order. + */ + // TODO: caveats about equivalence-relation? + boolean equals(@Nullable Object object); + + /** + * Returns the hash code for this multiset. This is defined as the sum of + * + *
  (element == null ? 0 : element.hashCode()) ^ count(element)
+ * + * over all distinct elements in the multiset. It follows that a multiset and + * its entry set always have the same hash code. + */ + int hashCode(); + + /** + * {@inheritDoc} + * + *

It is recommended, though not mandatory, that this method return the + * result of invoking {@link #toString} on the {@link #entrySet}, yielding a + * result such as + *

+   *     [a x 3, c, d x 2, e]
+   * 
+ */ + String toString(); + + // Refined Collection Methods + + /** + * {@inheritDoc} + * + *

Elements that occur multiple times in the multiset will appear + * multiple times in this iterator, though not necessarily sequentially. + */ + Iterator iterator(); + + /** + * Determines whether this multiset contains the specified element. + * + *

This method refines {@link Collection#contains} to further specify that + * it may not throw an exception in response to {@code element} being + * null or of the wrong type. + * + * @param element the element to check for + * @return {@code true} if this multiset contains at least one occurrence of + * the element + */ + boolean contains(@Nullable Object element); + + /** + * Returns {@code true} if this multiset contains at least one occurrence of + * each element in the specified collection. + * + *

This method refines {@link Collection#containsAll} to further specify + * that it may not throw an exception in response to any of {@code + * elements} being null or of the wrong type. + * + *

Note: this method does not take into account the occurrence + * count of an element in the two collections; it may still return {@code + * true} even if {@code elements} contains several occurrences of an element + * and this multiset contains only one. This is no different than any other + * collection type like {@link List}, but it may be unexpected to the user of + * a multiset. + * + * @param elements the collection of elements to be checked for containment in + * this multiset + * @return {@code true} if this multiset contains at least one occurrence of + * each element contained in {@code elements} + * @throws NullPointerException if {@code elements} is null + */ + boolean containsAll(Collection elements); + + /** + * Adds a single occurrence of the specified element to this multiset. + * + *

This method refines {@link Collection#add}, which only ensures + * the presence of the element, to further specify that a successful call must + * always increment the count of the element, and the overall size of the + * collection, by one. + * + * @param element the element to add one occurrence of; may be null only if + * explicitly allowed by the implementation + * @return {@code true} always, since this call is required to modify the + * multiset, unlike other {@link Collection} types + * @throws NullPointerException if {@code element} is null and this + * implementation does not permit null elements + * @throws IllegalArgumentException if {@link Integer#MAX_VALUE} occurrences + * of {@code element} are already contained in this multiset + */ + boolean add(E element); + + /** + * Removes a single occurrence of the specified element from this + * multiset, if present. + * + *

This method refines {@link Collection#remove} to further specify that it + * may not throw an exception in response to {@code element} being null + * or of the wrong type. + * + * @param element the element to remove one occurrence of + * @return {@code true} if an occurrence was found and removed + */ + boolean remove(@Nullable Object element); + + /** + * {@inheritDoc} + * + *

This method refines {@link Collection#removeAll} to further specify that + * it may not throw an exception in response to any of {@code elements} + * being null or of the wrong type. + */ + boolean removeAll(Collection c); + + /** + * {@inheritDoc} + * + *

This method refines {@link Collection#retainAll} to further specify that + * it may not throw an exception in response to any of {@code elements} + * being null or of the wrong type. + */ + boolean retainAll(Collection c); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multisets.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multisets.java new file mode 100644 index 00000000000..951883b48e8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Multisets.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Objects; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Provides static utility methods for creating and working with {@link + * Multiset} instances. + * + * @author Kevin Bourrillion + * @author Mike Bostock + */ +@GwtCompatible +public final class Multisets { + private Multisets() {} + + /** + * Returns an unmodifiable view of the specified multiset. Query operations on + * the returned multiset "read through" to the specified multiset, and + * attempts to modify the returned multiset result in an + * {@link UnsupportedOperationException}. + * + *

The returned multiset will be serializable if the specified multiset is + * serializable. + * + * @param multiset the multiset for which an unmodifiable view is to be + * generated + * @return an unmodifiable view of the multiset + */ + public static Multiset unmodifiableMultiset( + Multiset multiset) { + return new UnmodifiableMultiset(multiset); + } + + private static class UnmodifiableMultiset + extends ForwardingMultiset implements Serializable { + final Multiset delegate; + + UnmodifiableMultiset(Multiset delegate) { + this.delegate = delegate; + } + + @SuppressWarnings("unchecked") + @Override protected Multiset delegate() { + // This is safe because all non-covariant methods are overriden + return (Multiset) delegate; + } + + transient Set elementSet; + + @Override public Set elementSet() { + Set es = elementSet; + return (es == null) + ? elementSet = Collections.unmodifiableSet(delegate.elementSet()) + : es; + } + + transient Set> entrySet; + + @SuppressWarnings("unchecked") + @Override public Set> entrySet() { + Set> es = entrySet; + return (es == null) + // Safe because the returned set is made unmodifiable and Entry + // itself is readonly + ? entrySet = (Set) Collections.unmodifiableSet(delegate.entrySet()) + : es; + } + + @SuppressWarnings("unchecked") + @Override public Iterator iterator() { + // Safe because the returned Iterator is made unmodifiable + return (Iterator) Iterators.unmodifiableIterator(delegate.iterator()); + } + + @Override public boolean add(E element) { + throw new UnsupportedOperationException(); + } + + @Override public int add(E element, int occurences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll(Collection elementsToAdd) { + throw new UnsupportedOperationException(); + } + + @Override public boolean remove(Object element) { + throw new UnsupportedOperationException(); + } + + @Override public int remove(Object element, int occurrences) { + throw new UnsupportedOperationException(); + } + + @Override public boolean removeAll(Collection elementsToRemove) { + throw new UnsupportedOperationException(); + } + + @Override public boolean retainAll(Collection elementsToRetain) { + throw new UnsupportedOperationException(); + } + + @Override public void clear() { + throw new UnsupportedOperationException(); + } + + @Override public int setCount(E element, int count) { + throw new UnsupportedOperationException(); + } + + @Override public boolean setCount(E element, int oldCount, int newCount) { + throw new UnsupportedOperationException(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an immutable multiset entry with the specified element and count. + * + * @param e the element to be associated with the returned entry + * @param n the count to be associated with the returned entry + * @throws IllegalArgumentException if {@code n} is negative + */ + public static Multiset.Entry immutableEntry( + @Nullable final E e, final int n) { + checkArgument(n >= 0); + return new AbstractEntry() { + public E getElement() { + return e; + } + public int getCount() { + return n; + } + }; + } + + /** + * Returns a multiset view of the specified set. The multiset is backed by the + * set, so changes to the set are reflected in the multiset, and vice versa. + * If the set is modified while an iteration over the multiset is in progress + * (except through the iterator's own {@code remove} operation) the results of + * the iteration are undefined. + * + *

The multiset supports element removal, which removes the corresponding + * element from the set. It does not support the {@code add} or {@code addAll} + * operations, nor does it support the use of {@code setCount} to add + * elements. + * + *

The returned multiset will be serializable if the specified set is + * serializable. The multiset is threadsafe if the set is threadsafe. + * + * @param set the backing set for the returned multiset view + */ + static Multiset forSet(Set set) { + return new SetMultiset(set); + } + + /** @see Multisets#forSet */ + private static class SetMultiset extends ForwardingCollection + implements Multiset, Serializable { + final Set delegate; + + SetMultiset(Set set) { + delegate = checkNotNull(set); + } + + @Override protected Set delegate() { + return delegate; + } + + public int count(Object element) { + return delegate.contains(element) ? 1 : 0; + } + + public int add(E element, int occurrences) { + throw new UnsupportedOperationException(); + } + + public int remove(Object element, int occurrences) { + if (occurrences == 0) { + return count(element); + } + checkArgument(occurrences > 0); + return delegate.remove(element) ? 1 : 0; + } + + transient Set elementSet; + + public Set elementSet() { + Set es = elementSet; + return (es == null) ? elementSet = new ElementSet() : es; + } + + transient Set> entrySet; + + public Set> entrySet() { + Set> es = entrySet; + return (es == null) ? entrySet = new EntrySet() : es; + } + + @Override public boolean add(E o) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public int setCount(E element, int count) { + checkNonnegative(count, "count"); + + if (count == count(element)) { + return count; + } else if (count == 0) { + remove(element); + return 1; + } else { + throw new UnsupportedOperationException(); + } + } + + public boolean setCount(E element, int oldCount, int newCount) { + return setCountImpl(this, element, oldCount, newCount); + } + + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Multiset) { + Multiset that = (Multiset) object; + return this.size() == that.size() && delegate.equals(that.elementSet()); + } + return false; + } + + @Override public int hashCode() { + int sum = 0; + for (E e : this) { + sum += ((e == null) ? 0 : e.hashCode()) ^ 1; + } + return sum; + } + + /** @see SetMultiset#elementSet */ + class ElementSet extends ForwardingSet { + @Override protected Set delegate() { + return delegate; + } + + @Override public boolean add(E o) { + throw new UnsupportedOperationException(); + } + + @Override public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + } + + /** @see SetMultiset#entrySet */ + class EntrySet extends AbstractSet> { + @Override public int size() { + return delegate.size(); + } + @Override public Iterator> iterator() { + return new Iterator>() { + final Iterator elements = delegate.iterator(); + + public boolean hasNext() { + return elements.hasNext(); + } + public Entry next() { + return immutableEntry(elements.next(), 1); + } + public void remove() { + elements.remove(); + } + }; + } + // TODO: faster contains, remove + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns the expected number of distinct elements given the specified + * elements. The number of distinct elements is only computed if {@code + * elements} is an instance of {@code Multiset}; otherwise the default value + * of 11 is returned. + */ + static int inferDistinctElements(Iterable elements) { + if (elements instanceof Multiset) { + return ((Multiset) elements).elementSet().size(); + } + return 11; // initial capacity will be rounded up to 16 + } + + /** + * Implementation of the {@code equals}, {@code hashCode}, and + * {@code toString} methods of {@link Multiset.Entry}. + */ + abstract static class AbstractEntry implements Multiset.Entry { + /** + * Indicates whether an object equals this entry, following the behavior + * specified in {@link Multiset.Entry#equals}. + */ + @Override public boolean equals(@Nullable Object object) { + if (object instanceof Multiset.Entry) { + Multiset.Entry that = (Multiset.Entry) object; + return this.getCount() == that.getCount() + && Objects.equal(this.getElement(), that.getElement()); + } + return false; + } + + /** + * Return this entry's hash code, following the behavior specified in + * {@link Multiset.Entry#hashCode}. + */ + @Override public int hashCode() { + E e = getElement(); + return ((e == null) ? 0 : e.hashCode()) ^ getCount(); + } + + /** + * Returns a string representation of this multiset entry. The string + * representation consists of the associated element if the associated count + * is one, and otherwise the associated element followed by the characters + * " x " (space, x and space) followed by the count. Elements and counts are + * converted to strings as by {@code String.valueOf}. + */ + @Override public String toString() { + String text = String.valueOf(getElement()); + int n = getCount(); + return (n == 1) ? text : (text + " x " + n); + } + } + + static int setCountImpl(Multiset self, E element, int count) { + checkNonnegative(count, "count"); + + int oldCount = self.count(element); + + int delta = count - oldCount; + if (delta > 0) { + self.add(element, delta); + } else if (delta < 0) { + self.remove(element, -delta); + } + + return oldCount; + } + + static boolean setCountImpl( + Multiset self, E element, int oldCount, int newCount) { + checkNonnegative(oldCount, "oldCount"); + checkNonnegative(newCount, "newCount"); + + if (self.count(element) == oldCount) { + self.setCount(element, newCount); + return true; + } else { + return false; + } + } + + static void checkNonnegative(int count, String name) { + checkArgument(count >= 0, "%s cannot be negative: %s", name, count); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MutableClassToInstanceMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MutableClassToInstanceMap.java new file mode 100644 index 00000000000..e177ad8556c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/MutableClassToInstanceMap.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import java.util.HashMap; +import java.util.Map; + +/** + * A mutable class-to-instance map backed by an arbitrary user-provided map. + * See also {@link ImmutableClassToInstanceMap}. + * + * @author Kevin Bourrillion + */ +public final class MutableClassToInstanceMap + extends ConstrainedMap, B> + implements ClassToInstanceMap { + + /** + * Returns a new {@code MutableClassToInstanceMap} instance backed by a {@link + * HashMap} using the default initial capacity and load factor. + */ + public static MutableClassToInstanceMap create() { + return new MutableClassToInstanceMap( + new HashMap, B>()); + } + + /** + * Returns a new {@code MutableClassToInstanceMap} instance backed by a given + * empty {@code backingMap}. The caller surrenders control of the backing map, + * and thus should not allow any direct references to it to remain accessible. + */ + public static MutableClassToInstanceMap create( + Map, B> backingMap) { + return new MutableClassToInstanceMap(backingMap); + } + + private MutableClassToInstanceMap(Map, B> delegate) { + super(delegate, VALUE_CAN_BE_CAST_TO_KEY); + } + + private static final MapConstraint, Object> VALUE_CAN_BE_CAST_TO_KEY + = new MapConstraint, Object>() { + public void checkKeyValue(Class key, Object value) { + cast(key, value); + } + }; + + public T putInstance(Class type, T value) { + return cast(type, put(type, value)); + } + + public T getInstance(Class type) { + return cast(type, get(type)); + } + + // Default access so that ImmutableClassToInstanceMap can share it + static T cast(Class type, B value) { + // TODO: this should eventually use common.primitives.Primitives.wrap() + return wrap(type).cast(value); + } + + // safe because both Long.class and long.class are of type Class + @SuppressWarnings("unchecked") + private static Class wrap(Class c) { + return c.isPrimitive() ? (Class) PRIMITIVES_TO_WRAPPERS.get(c) : c; + } + + private static final Map, Class> PRIMITIVES_TO_WRAPPERS + = new ImmutableMap.Builder, Class>() + .put(boolean.class, Boolean.class) + .put(byte.class, Byte.class) + .put(char.class, Character.class) + .put(double.class, Double.class) + .put(float.class, Float.class) + .put(int.class, Integer.class) + .put(long.class, Long.class) + .put(short.class, Short.class) + .put(void.class, Void.class) + .build(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NaturalOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NaturalOrdering.java new file mode 100644 index 00000000000..41033805d7b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NaturalOrdering.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +/** An ordering that uses the natural order of the values. */ +@GwtCompatible(serializable = true) +@SuppressWarnings("unchecked") // TODO: the right way to explain this?? +final class NaturalOrdering + extends Ordering implements Serializable { + static final NaturalOrdering INSTANCE = new NaturalOrdering(); + + public int compare(Comparable left, Comparable right) { + checkNotNull(right); // left null is caught later + if (left == right) { + return 0; + } + + @SuppressWarnings("unchecked") // we're permitted to throw CCE + int result = left.compareTo(right); + return result; + } + + @SuppressWarnings("unchecked") // TODO: the right way to explain this?? + @Override public Ordering reverse() { + return (Ordering) ReverseNaturalOrdering.INSTANCE; + } + + // Override to remove a level of indirection from inner loop + @SuppressWarnings("unchecked") // TODO: the right way to explain this?? + @Override public int binarySearch( + List sortedList, Comparable key) { + return Collections.binarySearch((List) sortedList, key); + } + + // Override to remove a level of indirection from inner loop + @Override public List sortedCopy( + Iterable iterable) { + List list = Lists.newArrayList(iterable); + Collections.sort(list); + return list; + } + + // preserving singleton-ness gives equals()/hashCode() for free + private Object readResolve() { + return INSTANCE; + } + + @Override public String toString() { + return "Ordering.natural()"; + } + + private NaturalOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullOutputException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullOutputException.java new file mode 100644 index 00000000000..a5cf8101100 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullOutputException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +/** + * Thrown when a computer function returns null. This subclass exists so + * that our ReferenceCache adapter can differentiate null output from null + * keys, but we don't want to make this public otherwise. + * + * @author Bob Lee + */ +class NullOutputException extends NullPointerException { + public NullOutputException(String s) { + super(s); + } + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullsFirstOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullsFirstOrdering.java new file mode 100644 index 00000000000..8d93dbce714 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullsFirstOrdering.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** An ordering that treats {@code null} as less than all other values. */ +@GwtCompatible(serializable = true) +final class NullsFirstOrdering extends Ordering implements Serializable { + final Ordering ordering; + + NullsFirstOrdering(Ordering ordering) { + this.ordering = ordering; + } + + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return RIGHT_IS_GREATER; + } + if (right == null) { + return LEFT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsLast(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override public Ordering nullsFirst() { + return (Ordering) this; + } + + @Override public Ordering nullsLast() { + return ordering.nullsLast(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsFirstOrdering) { + NullsFirstOrdering that = (NullsFirstOrdering) object; + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override public int hashCode() { + return ordering.hashCode() ^ 957692532; // meaningless + } + + @Override public String toString() { + return ordering + ".nullsFirst()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullsLastOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullsLastOrdering.java new file mode 100644 index 00000000000..68fd8d1e4c5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/NullsLastOrdering.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** An ordering that treats {@code null} as greater than all other values. */ +@GwtCompatible(serializable = true) +final class NullsLastOrdering extends Ordering implements Serializable { + final Ordering ordering; + + NullsLastOrdering(Ordering ordering) { + this.ordering = ordering; + } + + public int compare(T left, T right) { + if (left == right) { + return 0; + } + if (left == null) { + return LEFT_IS_GREATER; + } + if (right == null) { + return RIGHT_IS_GREATER; + } + return ordering.compare(left, right); + } + + @Override public Ordering reverse() { + // ordering.reverse() might be optimized, so let it do its thing + return ordering.reverse().nullsFirst(); + } + + @Override public Ordering nullsFirst() { + return ordering.nullsFirst(); + } + + @SuppressWarnings("unchecked") // still need the right way to explain this + @Override public Ordering nullsLast() { + return (Ordering) this; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof NullsLastOrdering) { + NullsLastOrdering that = (NullsLastOrdering) object; + return this.ordering.equals(that.ordering); + } + return false; + } + + @Override public int hashCode() { + return ordering.hashCode() ^ -921210296; // meaningless + } + + @Override public String toString() { + return ordering + ".nullsLast()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ObjectArrays.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ObjectArrays.java new file mode 100644 index 00000000000..657503885d7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ObjectArrays.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; + +import java.util.Collection; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to object arrays. + * + * @author Kevin Bourrillion + */ +@GwtCompatible +public final class ObjectArrays { + private ObjectArrays() {} + + /** + * Returns a new array of the given length with the specified component type. + * + * @param type the component type + * @param length the length of the new array + */ + @GwtIncompatible("Array.newInstance(Class, int)") + @SuppressWarnings("unchecked") + public static T[] newArray(Class type, int length) { + return Platform.newArray(type, length); + } + + /** + * Returns a new array of the given length with the same type as a reference + * array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + public static T[] newArray(T[] reference, int length) { + return Platform.newArray(reference, length); + } + + /** + * Returns a new array that contains the concatenated contents of two arrays. + * + * @param first the first array of elements to concatenate + * @param second the second array of elements to concatenate + * @param type the component type of the returned array + */ + @GwtIncompatible("Array.newInstance(Class, int)") + public static T[] concat(T[] first, T[] second, Class type) { + T[] result = newArray(type, first.length + second.length); + System.arraycopy(first, 0, result, 0, first.length); + System.arraycopy(second, 0, result, first.length, second.length); + return result; + } + + /** + * Returns a new array that prepends {@code element} to {@code array}. + * + * @param element the element to prepend to the front of {@code array} + * @param array the array of elements to append + * @return an array whose size is one larger than {@code array}, with + * {@code element} occupying the first position, and the + * elements of {@code array} occupying the remaining elements. + */ + public static T[] concat(@Nullable T element, T[] array) { + T[] result = newArray(array, array.length + 1); + result[0] = element; + System.arraycopy(array, 0, result, 1, array.length); + return result; + } + + /** + * Returns a new array that appends {@code element} to {@code array}. + * + * @param array the array of elements to prepend + * @param element the element to append to the end + * @return an array whose size is one larger than {@code array}, with + * the same contents as {@code array}, plus {@code element} occupying the + * last position. + */ + public static T[] concat(T[] array, @Nullable T element) { + T[] result = arraysCopyOf(array, array.length + 1); + result[array.length] = element; + return result; + } + + /** GWT safe version of Arrays.copyOf. */ + private static T[] arraysCopyOf(T[] original, int newLength) { + T[] copy = newArray(original, newLength); + System.arraycopy( + original, 0, copy, 0, Math.min(original.length, newLength)); + return copy; + } + + /** + * Returns an array containing all of the elements in the specified + * collection; the runtime type of the returned array is that of the specified + * array. 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 the specified 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. + * + *

This method returns the elements in the order they are returned by the + * collection's iterator. + * + *

TODO: Support concurrent collections whose size can change while the + * method is running. + * + * @param c the collection for which to return an array of elements + * @param array the array in which to place the collection elements + * @throws ArrayStoreException if the runtime type of the specified array is + * not a supertype of the runtime type of every element in the specified + * collection + */ + static T[] toArrayImpl(Collection c, T[] array) { + int size = c.size(); + if (array.length < size) { + array = newArray(array, size); + } + fillArray(c, array); + if (array.length > size) { + array[size] = null; + } + return array; + } + + /** + * Returns an array containing all of the elements in the specified + * collection. This method returns the elements in the order they are returned + * by the collection's iterator. The returned array is "safe" in that no + * references to it are maintained by the collection. The caller is thus free + * to modify the returned array. + * + *

This method assumes that the collection size doesn't change while the + * method is running. + * + *

TODO: Support concurrent collections whose size can change while the + * method is running. + * + * @param c the collection for which to return an array of elements + */ + static Object[] toArrayImpl(Collection c) { + return fillArray(c, new Object[c.size()]); + } + + private static Object[] fillArray(Iterable elements, Object[] array) { + int i = 0; + for (Object element : elements) { + array[i++] = element; + } + return array; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Ordering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Ordering.java new file mode 100644 index 00000000000..f07d038c371 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Ordering.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import org.elasticsearch.util.gcommon.base.Function; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * A comparator with added methods to support common functions. For example: + *

   {@code
+ *
+ *   if (Ordering.from(comparator).reverse().isOrdered(list)) { ... }}
+ * + *

The {@link #from(Comparator)} method returns the equivalent {@code + * Ordering} instance for a pre-existing comparator. You can also skip the + * comparator step and extend {@code Ordering} directly:

   {@code
+ *
+ *   Ordering byLengthOrdering = new Ordering() {
+ *     public int compare(String left, String right) {
+ *       return Ints.compare(left.length(), right.length());
+ *     }
+ *   };}
+ * + * Except as noted, the orderings returned by the factory methods of this + * class are serializable if and only if the provided instances that back them + * are. For example, if {@code ordering} and {@code function} can themselves be + * serialized, then {@code ordering.onResultOf(function)} can as well. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible +public abstract class Ordering implements Comparator { + // Static factories + + /** + * Returns a serializable ordering that uses the natural order of the values. + * The ordering throws a {@link NullPointerException} when passed a null + * parameter. + * + *

The type specification is {@code }, instead of + * the technically correct {@code >}, to + * support legacy types from before Java 5. + */ + @GwtCompatible(serializable = true) + @SuppressWarnings("unchecked") // TODO: the right way to explain this?? + public static Ordering natural() { + return (Ordering) NaturalOrdering.INSTANCE; + } + + /** + * Returns an ordering for a pre-existing {@code comparator}. Note + * that if the comparator is not pre-existing, and you don't require + * serialization, you can subclass {@code Ordering} and implement its + * {@link #compare(Object, Object) compare} method instead. + * + * @param comparator the comparator that defines the order + */ + @GwtCompatible(serializable = true) + public static Ordering from(Comparator comparator) { + return (comparator instanceof Ordering) + ? (Ordering) comparator + : new ComparatorOrdering(comparator); + } + + /** + * Simply returns its argument. + * + * @deprecated no need to use this + */ + @GwtCompatible(serializable = true) + @Deprecated public static Ordering from(Ordering ordering) { + return checkNotNull(ordering); + } + + /** + * Returns an ordering that compares objects according to the order in + * which they appear in the given list. Only objects present in the list + * (according to {@link Object#equals}) may be compared. This comparator + * imposes a "partial ordering" over the type {@code T}. Subsequent changes + * to the {@code valuesInOrder} list will have no effect on the returned + * comparator. Null values in the list are not supported. + * + *

The returned comparator throws an {@link ClassCastException} when it + * receives an input parameter that isn't among the provided values. + * + *

The generated comparator is serializable if all the provided values are + * serializable. + * + * @param valuesInOrder the values that the returned comparator will be able + * to compare, in the order the comparator should induce + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if {@code valuesInOrder} contains any + * duplicate values (according to {@link Object#equals}) + */ + @GwtCompatible(serializable = true) + public static Ordering explicit(List valuesInOrder) { + return new ExplicitOrdering(valuesInOrder); + } + + /** + * Returns an ordering that compares objects according to the order in + * which they are given to this method. Only objects present in the argument + * list (according to {@link Object#equals}) may be compared. This comparator + * imposes a "partial ordering" over the type {@code T}. Null values in the + * argument list are not supported. + * + *

The returned comparator throws a {@link ClassCastException} when it + * receives an input parameter that isn't among the provided values. + * + *

The generated comparator is serializable if all the provided values are + * serializable. + * + * @param leastValue the value which the returned comparator should consider + * the "least" of all values + * @param remainingValuesInOrder the rest of the values that the returned + * comparator will be able to compare, in the order the comparator should + * follow + * @return the comparator described above + * @throws NullPointerException if any of the provided values is null + * @throws IllegalArgumentException if any duplicate values (according to + * {@link Object#equals(Object)}) are present among the method arguments + */ + @GwtCompatible(serializable = true) + public static Ordering explicit( + T leastValue, T... remainingValuesInOrder) { + return explicit(Lists.asList(leastValue, remainingValuesInOrder)); + } + + /** + * Exception thrown by a {@link Ordering#explicit(List)} or {@link + * Ordering#explicit(Object, Object[])} comparator when comparing a value + * outside the set of values it can compare. Extending {@link + * ClassCastException} may seem odd, but it is required. + */ + // TODO: consider making this exception type public. or consider getting rid + // of it. + @VisibleForTesting + static class IncomparableValueException extends ClassCastException { + final Object value; + + IncomparableValueException(Object value) { + super("Cannot compare value: " + value); + this.value = value; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns an ordering that compares objects by the natural ordering of their + * string representations as returned by {@code toString()}. It does not + * support null values. + * + *

The comparator is serializable. + */ + @GwtCompatible(serializable = true) + public static Ordering usingToString() { + return UsingToStringOrdering.INSTANCE; + } + + /** + * Returns an ordering which tries each given comparator in order until a + * non-zero result is found, returning that result, and returning zero only if + * all comparators return zero. The returned ordering is based on the state of + * the {@code comparators} iterable at the time it was provided to this + * method. + * + *

The returned ordering is equivalent to that produced using {@code + * Ordering.from(comp1).compound(comp2).compound(comp3) . . .}. + * + *

Warning: Supplying an argument with undefined iteration order, + * such as a {@link HashSet}, will produce non-deterministic results. + * + * @param comparators the comparators to try in order + */ + @GwtCompatible(serializable = true) + public static Ordering compound( + Iterable> comparators) { + return new CompoundOrdering(comparators); + } + + /** + * Constructs a new instance of this class (only invokable by the subclass + * constructor, typically implicit). + */ + protected Ordering() {} + + // Non-static factories + + /** + * Returns an ordering which first uses the ordering {@code this}, but which + * in the event of a "tie", then delegates to {@code secondaryComparator}. + * For example, to sort a bug list first by status and second by priority, you + * might use {@code byStatus.compound(byPriority)}. For a compound ordering + * with three or more components, simply chain multiple calls to this method. + * + *

An ordering produced by this method, or a chain of calls to this method, + * is equivalent to one created using {@link Ordering#compound(Iterable)} on + * the same component comparators. + */ + @GwtCompatible(serializable = true) + public Ordering compound( + Comparator secondaryComparator) { + return new CompoundOrdering(this, checkNotNull(secondaryComparator)); + } + + /** + * Returns the reverse of this ordering; the {@code Ordering} equivalent to + * {@link Collections#reverseOrder(Comparator)}. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().reverse(); + @GwtCompatible(serializable = true) + public Ordering reverse() { + return new ReverseOrdering(this); + } + + /** + * Returns a new ordering on {@code F} which orders elements by first applying + * a function to them, then comparing those results using {@code this}. For + * example, to compare objects by their string forms, in a case-insensitive + * manner, use:

   {@code
+   *
+   *   Ordering.from(String.CASE_INSENSITIVE_ORDER)
+   *       .onResultOf(Functions.toStringFunction())}
+ */ + @GwtCompatible(serializable = true) + public Ordering onResultOf(Function function) { + return new ByFunctionOrdering(function, this); + } + + /** + * Returns an ordering that treats {@code null} as less than all other values + * and uses {@code this} to compare non-null values. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsFirst(); + @GwtCompatible(serializable = true) + public Ordering nullsFirst() { + return new NullsFirstOrdering(this); + } + + /** + * Returns an ordering that treats {@code null} as greater than all other + * values and uses this ordering to compare non-null values. + */ + // type parameter lets us avoid the extra in statements like: + // Ordering o = Ordering.natural().nullsLast(); + @GwtCompatible(serializable = true) + public Ordering nullsLast() { + return new NullsLastOrdering(this); + } + + /** + * {@link Collections#binarySearch(List, Object, Comparator) Searches} + * {@code sortedList} for {@code key} using the binary search algorithm. The + * list must be sorted using this ordering. + * + * @param sortedList the list to be searched + * @param key the key to be searched for + */ + public int binarySearch(List sortedList, T key) { + return Collections.binarySearch(sortedList, key, this); + } + + /** + * Returns a copy of the given iterable sorted by this ordering. The input is + * not modified. The returned list is modifiable, serializable, and has random + * access. + * + *

Unlike {@link Sets#newTreeSet(Iterable)}, this method does not collapse + * elements that compare as zero, and the resulting collection does not + * maintain its own sort order. + * + * @param iterable the elements to be copied and sorted + * @return a new list containing the given elements in sorted order + */ + public List sortedCopy(Iterable iterable) { + List list = Lists.newArrayList(iterable); + Collections.sort(list, this); + return list; + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is + * greater than or equal to the element that preceded it, according to this + * ordering. Note that this is always true when the iterable has fewer than + * two elements. + */ + public boolean isOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) > 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns {@code true} if each element in {@code iterable} after the first is + * strictly greater than the element that preceded it, according to + * this ordering. Note that this is always true when the iterable has fewer + * than two elements. + */ + public boolean isStrictlyOrdered(Iterable iterable) { + Iterator it = iterable.iterator(); + if (it.hasNext()) { + T prev = it.next(); + while (it.hasNext()) { + T next = it.next(); + if (compare(prev, next) >= 0) { + return false; + } + prev = next; + } + } + return true; + } + + /** + * Returns the largest of the specified values according to this ordering. If + * there are multiple largest values, the first of those is returned. + * + * @param iterable the iterable whose maximum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E max(Iterable iterable) { + Iterator iterator = iterable.iterator(); + + // let this throw NoSuchElementException as necessary + E maxSoFar = iterator.next(); + + while (iterator.hasNext()) { + maxSoFar = max(maxSoFar, iterator.next()); + } + + return maxSoFar; + } + + /** + * Returns the largest of the specified values according to this ordering. If + * there are multiple largest values, the first of those is returned. + * + * @param a value to compare, returned if greater than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E max(E a, E b, E c, E... rest) { + E maxSoFar = max(max(a, b), c); + + for (E r : rest) { + maxSoFar = max(maxSoFar, r); + } + + return maxSoFar; + } + + /** + * Returns the larger of the two values according to this ordering. If the + * values compare as 0, the first is returned. + * + *

Implementation note: this method is invoked by the default + * implementations of the other {@code max} overloads, so overriding it will + * affect their behavior. + * + * @param a value to compare, returned if greater than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E max(E a, E b) { + return compare(a, b) >= 0 ? a : b; + } + + /** + * Returns the smallest of the specified values according to this ordering. If + * there are multiple smallest values, the first of those is returned. + * + * @param iterable the iterable whose minimum element is to be determined + * @throws NoSuchElementException if {@code iterable} is empty + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E min(Iterable iterable) { + Iterator iterator = iterable.iterator(); + + // let this throw NoSuchElementException as necessary + E minSoFar = iterator.next(); + + while (iterator.hasNext()) { + minSoFar = min(minSoFar, iterator.next()); + } + + return minSoFar; + } + + /** + * Returns the smallest of the specified values according to this ordering. If + * there are multiple smallest values, the first of those is returned. + * + * @param a value to compare, returned if less than or equal to the rest. + * @param b value to compare + * @param c value to compare + * @param rest values to compare + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E min(E a, E b, E c, E... rest) { + E minSoFar = min(min(a, b), c); + + for (E r : rest) { + minSoFar = min(minSoFar, r); + } + + return minSoFar; + } + + /** + * Returns the smaller of the two values according to this ordering. If the + * values compare as 0, the first is returned. + * + *

Implementation note: this method is invoked by the default + * implementations of the other {@code min} overloads, so overriding it will + * affect their behavior. + * + * @param a value to compare, returned if less than or equal to b. + * @param b value to compare. + * @throws ClassCastException if the parameters are not mutually + * comparable under this ordering. + */ + public E min(E a, E b) { + return compare(a, b) <= 0 ? a : b; + } + + // Never make these public + static final int LEFT_IS_GREATER = 1; + static final int RIGHT_IS_GREATER = -1; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/PeekingIterator.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/PeekingIterator.java new file mode 100644 index 00000000000..eca3b7ae51b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/PeekingIterator.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * An iterator that supports a one-element lookahead while iterating. + * + * @author Mick Killianey + */ +@GwtCompatible +public interface PeekingIterator extends Iterator { + /** + * Returns the next element in the iteration, without advancing the iteration. + * + *

Calls to {@code peek()} should not change the state of the iteration, + * except that it may prevent removal of the most recent element via + * {@link #remove()}. + * + * @throws NoSuchElementException if the iteration has no more elements + * according to {@link #hasNext()} + */ + E peek(); + + /** + * {@inheritDoc} + * + *

The objects returned by consecutive calls to {@link #peek()} then {@link + * #next()} are guaranteed to be equal to each other. + */ + E next(); + + /** + * {@inheritDoc} + * + *

Implementations may or may not support removal when a call to {@link + * #peek()} has occurred since the most recent call to {@link #next()}. + * + * @throws IllegalStateException if there has been a call to {@link #peek()} + * since the most recent call to {@link #next()} and this implementation + * does not support this sequence of calls (optional) + */ + void remove(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Platform.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Platform.java new file mode 100644 index 00000000000..6414f8c0fa9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Platform.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; + +import java.lang.reflect.Array; +import java.util.List; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + * @author Hayward Chan + */ +@GwtCompatible(emulated = true) +class Platform { + + /** + * Calls {@link List#subList(int, int)}. Factored out so that it can be + * emulated in GWT. + * + *

This method is not supported in GWT yet. See + * GWT issue 1791 + */ + @GwtIncompatible("List.subList") + static List subList(List list, int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + + /** + * Calls {@link Class#isInstance(Object)}. Factored out so that it can be + * emulated in GWT. + */ + @GwtIncompatible("Class.isInstance") + static boolean isInstance(Class clazz, Object obj) { + return clazz.isInstance(obj); + } + + /** + * Clone the given array using {@link Object#clone()}. It is factored out so + * that it can be emulated in GWT. + */ + static T[] clone(T[] array) { + return array.clone(); + } + + /** + * Returns a new array of the given length with the specified component type. + * + * @param type the component type + * @param length the length of the new array + */ + @GwtIncompatible("Array.newInstance(Class, int)") + @SuppressWarnings("unchecked") + static T[] newArray(Class type, int length) { + return (T[]) Array.newInstance(type, length); + } + + /** + * Returns a new array of the given length with the same type as a reference + * array. + * + * @param reference any array of the desired type + * @param length the length of the new array + */ + static T[] newArray(T[] reference, int length) { + Class type = reference.getClass().getComponentType(); + + // the cast is safe because + // result.getClass() == reference.getClass().getComponentType() + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(type, length); + return result; + } + + private Platform() {} +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableBiMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableBiMap.java new file mode 100644 index 00000000000..6841470f7b1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableBiMap.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +/** + * Bimap with one or more mappings. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +class RegularImmutableBiMap extends ImmutableBiMap { + final transient ImmutableMap delegate; + final transient ImmutableBiMap inverse; + + RegularImmutableBiMap(ImmutableMap delegate) { + this.delegate = delegate; + + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Entry entry : delegate.entrySet()) { + builder.put(entry.getValue(), entry.getKey()); + } + ImmutableMap backwardMap = builder.build(); + this.inverse = new RegularImmutableBiMap(backwardMap, this); + } + + RegularImmutableBiMap(ImmutableMap delegate, + ImmutableBiMap inverse) { + this.delegate = delegate; + this.inverse = inverse; + } + + @Override ImmutableMap delegate() { + return delegate; + } + + @Override public ImmutableBiMap inverse() { + return inverse; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableList.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableList.java new file mode 100644 index 00000000000..31063216ac4 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableList.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Preconditions; + +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableList} with one or more elements. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class RegularImmutableList extends ImmutableList { + private final transient int offset; + private final transient int size; + private final transient Object[] array; + + RegularImmutableList(Object[] array, int offset, int size) { + this.offset = offset; + this.size = size; + this.array = array; + } + + RegularImmutableList(Object[] array) { + this(array, 0, array.length); + } + + public int size() { + return size; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean contains(Object target) { + return indexOf(target) != -1; + } + + // The fake cast to E is safe because the creation methods only allow E's + @SuppressWarnings("unchecked") + @Override public UnmodifiableIterator iterator() { + return (UnmodifiableIterator) Iterators.forArray(array, offset, size); + } + + @Override public Object[] toArray() { + Object[] newArray = new Object[size()]; + System.arraycopy(array, offset, newArray, 0, size); + return newArray; + } + + @Override public T[] toArray(T[] other) { + if (other.length < size) { + other = ObjectArrays.newArray(other, size); + } else if (other.length > size) { + other[size] = null; + } + System.arraycopy(array, offset, other, 0, size); + return other; + } + + // The fake cast to E is safe because the creation methods only allow E's + @SuppressWarnings("unchecked") + public E get(int index) { + Preconditions.checkElementIndex(index, size); + return (E) array[index + offset]; + } + + @Override public int indexOf(Object target) { + if (target != null) { + for (int i = offset; i < offset + size; i++) { + if (array[i].equals(target)) { + return i - offset; + } + } + } + return -1; + } + + @Override public int lastIndexOf(Object target) { + if (target != null) { + for (int i = offset + size - 1; i >= offset; i--) { + if (array[i].equals(target)) { + return i - offset; + } + } + } + return -1; + } + + @Override public ImmutableList subList(int fromIndex, int toIndex) { + Preconditions.checkPositionIndexes(fromIndex, toIndex, size); + return (fromIndex == toIndex) + ? ImmutableList.of() + : new RegularImmutableList( + array, offset + fromIndex, toIndex - fromIndex); + } + + public ListIterator listIterator() { + return listIterator(0); + } + + public ListIterator listIterator(final int start) { + Preconditions.checkPositionIndex(start, size); + + return new ListIterator() { + int index = start; + + public boolean hasNext() { + return index < size; + } + public boolean hasPrevious() { + return index > 0; + } + + public int nextIndex() { + return index; + } + public int previousIndex() { + return index - 1; + } + + public E next() { + E result; + try { + result = get(index); + } catch (IndexOutOfBoundsException rethrown) { + throw new NoSuchElementException(); + } + index++; + return result; + } + public E previous() { + E result; + try { + result = get(index - 1); + } catch (IndexOutOfBoundsException rethrown) { + throw new NoSuchElementException(); + } + index--; + return result; + } + + public void set(E o) { + throw new UnsupportedOperationException(); + } + public void add(E o) { + throw new UnsupportedOperationException(); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof List)) { + return false; + } + + List that = (List) object; + if (this.size() != that.size()) { + return false; + } + + int index = offset; + if (object instanceof RegularImmutableList) { + RegularImmutableList other = (RegularImmutableList) object; + for (int i = other.offset; i < other.offset + other.size; i++) { + if (!array[index++].equals(other.array[i])) { + return false; + } + } + } else { + for (Object element : that) { + if (!array[index++].equals(element)) { + return false; + } + } + } + return true; + } + + @Override public int hashCode() { + // not caching hash code since it could change if the elements are mutable + // in a way that modifies their hash codes + int hashCode = 1; + for (int i = offset; i < offset + size; i++) { + hashCode = 31 * hashCode + array[i].hashCode(); + } + return hashCode; + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder(size() * 16); + sb.append('[').append(array[offset]); + for (int i = offset + 1; i < offset + size; i++) { + sb.append(", ").append(array[i]); + } + return sb.append(']').toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableMap.java new file mode 100644 index 00000000000..dd0e7c32e81 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableMap.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.collect.ImmutableSet.ArrayImmutableSet; +import org.elasticsearch.util.gcommon.collect.ImmutableSet.TransformedImmutableSet; + +/** + * Implementation of {@link ImmutableMap} with two or more entries. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +final class RegularImmutableMap extends ImmutableMap { + + private final transient Entry[] entries; // entries in insertion order + private final transient Object[] table; // alternating keys and values + // 'and' with an int then shift to get a table index + private final transient int mask; + private final transient int keySetHashCode; + + RegularImmutableMap(Entry... immutableEntries) { + // each of our 6 callers carefully put only Entrys into the array! + @SuppressWarnings("unchecked") + Entry[] tmp = (Entry[]) immutableEntries; + this.entries = tmp; + + int tableSize = Hashing.chooseTableSize(immutableEntries.length); + table = new Object[tableSize * 2]; + mask = tableSize - 1; + + int keySetHashCodeMutable = 0; + for (Entry entry : this.entries) { + K key = entry.getKey(); + int keyHashCode = key.hashCode(); + for (int i = Hashing.smear(keyHashCode); true; i++) { + int index = (i & mask) * 2; + Object existing = table[index]; + if (existing == null) { + V value = entry.getValue(); + table[index] = key; + table[index + 1] = value; + keySetHashCodeMutable += keyHashCode; + break; + } else if (existing.equals(key)) { + throw new IllegalArgumentException("duplicate key: " + key); + } + } + } + keySetHashCode = keySetHashCodeMutable; + } + + @Override public V get(Object key) { + if (key == null) { + return null; + } + for (int i = Hashing.smear(key.hashCode()); true; i++) { + int index = (i & mask) * 2; + Object candidate = table[index]; + if (candidate == null) { + return null; + } + if (candidate.equals(key)) { + // we're careful to store only V's at odd indices + @SuppressWarnings("unchecked") + V value = (V) table[index + 1]; + return value; + } + } + } + + public int size() { + return entries.length; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean containsValue(Object value) { + if (value == null) { + return false; + } + for (Entry entry : entries) { + if (entry.getValue().equals(value)) { + return true; + } + } + return false; + } + + // TODO: Serialization of the map views should serialize the map, and + // deserialization should call entrySet(), keySet(), or values() on the + // deserialized map. The views are serializable since the Immutable* classes + // are. + + private transient ImmutableSet> entrySet; + + @Override public ImmutableSet> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = new EntrySet(this)) : es; + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private static class EntrySet extends ArrayImmutableSet> { + final transient RegularImmutableMap map; + + EntrySet(RegularImmutableMap map) { + super(map.entries); + this.map = map; + } + + @Override public boolean contains(Object target) { + if (target instanceof Entry) { + Entry entry = (Entry) target; + V mappedValue = map.get(entry.getKey()); + return mappedValue != null && mappedValue.equals(entry.getValue()); + } + return false; + } + } + + private transient ImmutableSet keySet; + + @Override public ImmutableSet keySet() { + ImmutableSet ks = keySet; + return (ks == null) ? (keySet = new KeySet(this)) : ks; + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private static class KeySet + extends TransformedImmutableSet, K> { + final RegularImmutableMap map; + + KeySet(RegularImmutableMap map) { + super(map.entries, map.keySetHashCode); + this.map = map; + } + + @Override K transform(Entry element) { + return element.getKey(); + } + + @Override public boolean contains(Object target) { + return map.containsKey(target); + } + } + + private transient ImmutableCollection values; + + @Override public ImmutableCollection values() { + ImmutableCollection v = values; + return (v == null) ? (values = new Values(this)) : v; + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private static class Values extends ImmutableCollection { + final RegularImmutableMap map; + + Values(RegularImmutableMap map) { + this.map = map; + } + + public int size() { + return map.entries.length; + } + + @Override public UnmodifiableIterator iterator() { + return new AbstractIterator() { + int index = 0; + @Override protected V computeNext() { + return (index < map.entries.length) + ? map.entries[index++].getValue() + : endOfData(); + } + }; + } + + @Override public boolean contains(Object target) { + return map.containsValue(target); + } + } + + @Override public String toString() { + StringBuilder result = new StringBuilder(size() * 16).append('{'); + Collections2.standardJoiner.appendTo(result, entries); + return result.append('}').toString(); + } + + // This class is never actually serialized directly, but we have to make the + // warning go away (and suppressing would suppress for all nested classes too) + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableSet.java new file mode 100644 index 00000000000..e3f18925176 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableSet.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.VisibleForTesting; +import org.elasticsearch.util.gcommon.collect.ImmutableSet.ArrayImmutableSet; + +/** + * Implementation of {@link ImmutableSet} with two or more elements. + * + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class RegularImmutableSet extends ArrayImmutableSet { + // the same elements in hashed positions (plus nulls) + @VisibleForTesting final transient Object[] table; + // 'and' with an int to get a valid table index. + private final transient int mask; + private final transient int hashCode; + + RegularImmutableSet( + Object[] elements, int hashCode, Object[] table, int mask) { + super(elements); + this.table = table; + this.mask = mask; + this.hashCode = hashCode; + } + + @Override public boolean contains(Object target) { + if (target == null) { + return false; + } + for (int i = Hashing.smear(target.hashCode()); true; i++) { + Object candidate = table[i & mask]; + if (candidate == null) { + return false; + } + if (candidate.equals(target)) { + return true; + } + } + } + + @Override public int hashCode() { + return hashCode; + } + + @Override boolean isHashCodeFast() { + return true; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableSortedSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableSortedSet.java new file mode 100644 index 00000000000..34c244ba19f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/RegularImmutableSortedSet.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * An empty immutable sorted set with one or more elements. + * TODO: Consider creating a separate class for a single-element sorted set. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") +final class RegularImmutableSortedSet + extends ImmutableSortedSet { + + private final Object[] elements; + /** + * The index of the first element that's in the sorted set (inclusive + * index). + */ + private final int fromIndex; + /** + * The index after the last element that's in the sorted set (exclusive + * index). + */ + private final int toIndex; + + RegularImmutableSortedSet(Object[] elements, + Comparator comparator) { + super(comparator); + this.elements = elements; + this.fromIndex = 0; + this.toIndex = elements.length; + } + + RegularImmutableSortedSet(Object[] elements, + Comparator comparator, int fromIndex, int toIndex) { + super(comparator); + this.elements = elements; + this.fromIndex = fromIndex; + this.toIndex = toIndex; + } + + // The factory methods ensure that every element is an E. + @SuppressWarnings("unchecked") + @Override public UnmodifiableIterator iterator() { + return (UnmodifiableIterator) + Iterators.forArray(elements, fromIndex, size()); + } + + @Override public boolean isEmpty() { + return false; + } + + public int size() { + return toIndex - fromIndex; + } + + @Override public boolean contains(Object o) { + if (o == null) { + return false; + } + try { + return binarySearch(o) >= 0; + } catch (ClassCastException e) { + return false; + } + } + + @Override public boolean containsAll(Collection targets) { + // TODO: For optimal performance, use a binary search when + // targets.size() < size() / log(size()) + if (!hasSameComparator(targets, comparator()) || (targets.size() <= 1)) { + return super.containsAll(targets); + } + + /* + * If targets is a sorted set with the same comparator, containsAll can + * run in O(n) time stepping through the two collections. + */ + int i = fromIndex; + Iterator iterator = targets.iterator(); + Object target = iterator.next(); + + while (true) { + if (i >= toIndex) { + return false; + } + + int cmp = unsafeCompare(elements[i], target); + + if (cmp < 0) { + i++; + } else if (cmp == 0) { + if (!iterator.hasNext()) { + return true; + } + target = iterator.next(); + i++; + } else if (cmp > 0) { + return false; + } + } + } + + private int binarySearch(Object key) { + int lower = fromIndex; + int upper = toIndex - 1; + + while (lower <= upper) { + int middle = lower + (upper - lower) / 2; + int c = unsafeCompare(key, elements[middle]); + if (c < 0) { + upper = middle - 1; + } else if (c > 0) { + lower = middle + 1; + } else { + return middle; + } + } + + return -lower - 1; + } + + @Override public Object[] toArray() { + Object[] array = new Object[size()]; + System.arraycopy(elements, fromIndex, array, 0, size()); + return array; + } + + // TODO: Move to ObjectArrays (same code in ImmutableList). + @Override public T[] toArray(T[] array) { + int size = size(); + if (array.length < size) { + array = ObjectArrays.newArray(array, size); + } else if (array.length > size) { + array[size] = null; + } + System.arraycopy(elements, fromIndex, array, 0, size); + return array; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Set)) { + return false; + } + + Set that = (Set) object; + if (size() != that.size()) { + return false; + } + + if (hasSameComparator(that, comparator)) { + Iterator iterator = that.iterator(); + try { + for (int i = fromIndex; i < toIndex; i++) { + Object otherElement = iterator.next(); + if (otherElement == null + || unsafeCompare(elements[i], otherElement) != 0) { + return false; + } + } + return true; + } catch (ClassCastException e) { + return false; + } catch (NoSuchElementException e) { + return false; // concurrent change to other set + } + } + return this.containsAll(that); + } + + @Override public int hashCode() { + // not caching hash code since it could change if the elements are mutable + // in a way that modifies their hash codes + int hash = 0; + for (int i = fromIndex; i < toIndex; i++) { + hash += elements[i].hashCode(); + } + return hash; + } + + // The factory methods ensure that every element is an E. + @SuppressWarnings("unchecked") + public E first() { + return (E) elements[fromIndex]; + } + + // The factory methods ensure that every element is an E. + @SuppressWarnings("unchecked") + public E last() { + return (E) elements[toIndex - 1]; + } + + @Override ImmutableSortedSet headSetImpl(E toElement) { + return createSubset(fromIndex, findSubsetIndex(toElement)); + } + + @Override ImmutableSortedSet subSetImpl(E fromElement, E toElement) { + return createSubset( + findSubsetIndex(fromElement), findSubsetIndex(toElement)); + } + + @Override ImmutableSortedSet tailSetImpl(E fromElement) { + return createSubset(findSubsetIndex(fromElement), toIndex); + } + + private int findSubsetIndex(E element) { + int index = binarySearch(element); + return (index >= 0) ? index : (-index - 1); + } + + private ImmutableSortedSet createSubset( + int newFromIndex, int newToIndex) { + if (newFromIndex < newToIndex) { + return new RegularImmutableSortedSet(elements, comparator, + newFromIndex, newToIndex); + } else { + return emptySet(comparator); + } + } + + @Override boolean hasPartialArray() { + return (fromIndex != 0) || (toIndex != elements.length); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ReverseNaturalOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ReverseNaturalOrdering.java new file mode 100644 index 00000000000..a741e292053 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ReverseNaturalOrdering.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; + +/** An ordering that uses the reverse of the natural order of the values. */ +@GwtCompatible(serializable = true) +@SuppressWarnings("unchecked") // TODO: the right way to explain this?? +final class ReverseNaturalOrdering + extends Ordering implements Serializable { + static final ReverseNaturalOrdering INSTANCE = new ReverseNaturalOrdering(); + + public int compare(Comparable left, Comparable right) { + checkNotNull(left); // right null is caught later + if (left == right) { + return 0; + } + + @SuppressWarnings("unchecked") // we're permitted to throw CCE + int result = right.compareTo(left); + return result; + } + + @Override public Ordering reverse() { + return Ordering.natural(); + } + + // Override the six min/max methods to "hoist" delegation outside loops + + @Override public E min(E a, E b) { + return NaturalOrdering.INSTANCE.max(a, b); + } + + @Override public E min(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.max(a, b, c, rest); + } + + @Override public E min(Iterable iterable) { + return NaturalOrdering.INSTANCE.max(iterable); + } + + @Override public E max(E a, E b) { + return NaturalOrdering.INSTANCE.min(a, b); + } + + @Override public E max(E a, E b, E c, E... rest) { + return NaturalOrdering.INSTANCE.min(a, b, c, rest); + } + + @Override public E max(Iterable iterable) { + return NaturalOrdering.INSTANCE.min(iterable); + } + + // preserving singleton-ness gives equals()/hashCode() for free + private Object readResolve() { + return INSTANCE; + } + + @Override public String toString() { + return "Ordering.natural().reverse()"; + } + + private ReverseNaturalOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ReverseOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ReverseOrdering.java new file mode 100644 index 00000000000..aef550f8547 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/ReverseOrdering.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.Serializable; + +import javax.annotation.Nullable; + +/** An ordering that uses the reverse of a given order. */ +@GwtCompatible(serializable = true) +final class ReverseOrdering extends Ordering implements Serializable { + final Ordering forwardOrder; + + ReverseOrdering(Ordering forwardOrder) { + this.forwardOrder = checkNotNull(forwardOrder); + } + + public int compare(T a, T b) { + return forwardOrder.compare(b, a); + } + + @SuppressWarnings("unchecked") // how to explain? + @Override public Ordering reverse() { + return (Ordering) forwardOrder; + } + + // Override the six min/max methods to "hoist" delegation outside loops + + @Override public E min(E a, E b) { + return forwardOrder.max(a, b); + } + + @Override public E min(E a, E b, E c, E... rest) { + return forwardOrder.max(a, b, c, rest); + } + + @Override public E min(Iterable iterable) { + return forwardOrder.max(iterable); + } + + @Override public E max(E a, E b) { + return forwardOrder.min(a, b); + } + + @Override public E max(E a, E b, E c, E... rest) { + return forwardOrder.min(a, b, c, rest); + } + + @Override public E max(Iterable iterable) { + return forwardOrder.min(iterable); + } + + @Override public int hashCode() { + return -forwardOrder.hashCode(); + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof ReverseOrdering) { + ReverseOrdering that = (ReverseOrdering) object; + return this.forwardOrder.equals(that.forwardOrder); + } + return false; + } + + @Override public String toString() { + return forwardOrder + ".reverse()"; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Serialization.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Serialization.java new file mode 100644 index 00000000000..8c527a6497d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Serialization.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; + +/** + * Provides static methods for serializing collection classes. + * + *

This class assists the implementation of collection classes. Do not use + * this class to serialize collections that are defined elsewhere. + * + * @author Jared Levy + */ +@GwtCompatible(emulated = true) // Accessible but not supported in GWT. +final class Serialization { + private Serialization() {} + + /** + * Reads a count corresponding to a serialized map, multiset, or multimap. It + * returns the size of a map serialized by {@link + * #writeMap(Map, ObjectOutputStream)}, the number of distinct elements in a + * multiset serialized by {@link + * #writeMultiset(Multiset, ObjectOutputStream)}, or the number of distinct + * keys in a multimap serialized by {@link + * #writeMultimap(Multimap, ObjectOutputStream)}. + * + *

The returned count may be used to construct an empty collection of the + * appropriate capacity before calling any of the {@code populate} methods. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static int readCount(ObjectInputStream stream) throws IOException { + return stream.readInt(); + } + + /** + * Stores the contents of a map in an output stream, as part of serialization. + * It does not support concurrent maps whose content may change while the + * method is running. + * + *

The serialized output consists of the number of entries, first key, + * first value, second key, second value, and so on. + */ + @GwtIncompatible("java.io.ObjectOutputStream") + public static void writeMap(Map map, ObjectOutputStream stream) + throws IOException { + stream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + + /** + * Populates a map by reading an input stream, as part of deserialization. + * See {@link #writeMap} for the data format. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static void populateMap(Map map, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int size = stream.readInt(); + populateMap(map, stream, size); + } + + /** + * Populates a map by reading an input stream, as part of deserialization. + * See {@link #writeMap} for the data format. The size is determined by a + * prior call to {@link #readCount}. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static void populateMap(Map map, ObjectInputStream stream, + int size) throws IOException, ClassNotFoundException { + for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMap + K key = (K) stream.readObject(); + @SuppressWarnings("unchecked") // reading data stored by writeMap + V value = (V) stream.readObject(); + map.put(key, value); + } + } + + /** + * Stores the contents of a multiset in an output stream, as part of + * serialization. It does not support concurrent multisets whose content may + * change while the method is running. + * + *

The serialized output consists of the number of distinct elements, the + * first element, its count, the second element, its count, and so on. + */ + @GwtIncompatible("java.io.ObjectOutputStream") + public static void writeMultiset( + Multiset multiset, ObjectOutputStream stream) throws IOException { + int entryCount = multiset.entrySet().size(); + stream.writeInt(entryCount); + for (Multiset.Entry entry : multiset.entrySet()) { + stream.writeObject(entry.getElement()); + stream.writeInt(entry.getCount()); + } + } + + /** + * Populates a multiset by reading an input stream, as part of + * deserialization. See {@link #writeMultiset} for the data format. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static void populateMultiset( + Multiset multiset, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctElements = stream.readInt(); + populateMultiset(multiset, stream, distinctElements); + } + + /** + * Populates a multiset by reading an input stream, as part of + * deserialization. See {@link #writeMultiset} for the data format. The number + * of distinct elements is determined by a prior call to {@link #readCount}. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static void populateMultiset( + Multiset multiset, ObjectInputStream stream, int distinctElements) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctElements; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultiset + E element = (E) stream.readObject(); + int count = stream.readInt(); + multiset.add(element, count); + } + } + + /** + * Stores the contents of a multimap in an output stream, as part of + * serialization. It does not support concurrent multimaps whose content may + * change while the method is running. The {@link Multimap#asMap} view + * determines the ordering in which data is written to the stream. + * + *

The serialized output consists of the number of distinct keys, and then + * for each distinct key: the key, the number of values for that key, and the + * key's values. + */ + @GwtIncompatible("java.io.ObjectOutputStream") + public static void writeMultimap( + Multimap multimap, ObjectOutputStream stream) throws IOException { + stream.writeInt(multimap.asMap().size()); + for (Map.Entry> entry : multimap.asMap().entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeInt(entry.getValue().size()); + for (V value : entry.getValue()) { + stream.writeObject(value); + } + } + } + + /** + * Populates a multimap by reading an input stream, as part of + * deserialization. See {@link #writeMultimap} for the data format. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static void populateMultimap( + Multimap multimap, ObjectInputStream stream) + throws IOException, ClassNotFoundException { + int distinctKeys = stream.readInt(); + populateMultimap(multimap, stream, distinctKeys); + } + + /** + * Populates a multimap by reading an input stream, as part of + * deserialization. See {@link #writeMultimap} for the data format. The number + * of distinct keys is determined by a prior call to {@link #readCount}. + */ + @GwtIncompatible("java.io.ObjectInputStream") + public static void populateMultimap( + Multimap multimap, ObjectInputStream stream, int distinctKeys) + throws IOException, ClassNotFoundException { + for (int i = 0; i < distinctKeys; i++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + K key = (K) stream.readObject(); + Collection values = multimap.get(key); + int valueCount = stream.readInt(); + for (int j = 0; j < valueCount; j++) { + @SuppressWarnings("unchecked") // reading data stored by writeMultimap + V value = (V) stream.readObject(); + values.add(value); + } + } + } + + // Secret sauce for setting final fields; don't make it public. + @GwtIncompatible("java.lang.reflect.Field") + static FieldSetter getFieldSetter( + final Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + return new FieldSetter(field); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); // programmer error + } + } + + // Secret sauce for setting final fields; don't make it public. + @GwtCompatible(emulated = true) // Accessible but not supported in GWT. + static final class FieldSetter { + private final Field field; + + private FieldSetter(Field field) { + this.field = field; + field.setAccessible(true); + } + + @GwtIncompatible("java.lang.reflect.Field") + void set(T instance, Object value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + + @GwtIncompatible("java.lang.reflect.Field") + void set(T instance, int value) { + try { + field.set(instance, value); + } catch (IllegalAccessException impossible) { + throw new AssertionError(impossible); + } + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SetMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SetMultimap.java new file mode 100644 index 00000000000..580d2f3b13c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SetMultimap.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * A {@code Multimap} that cannot hold duplicate key-value pairs. Adding a + * key-value pair that's already in the multimap has no effect. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link Set} of values, while {@link #entries} returns a {@code + * Set} of map entries. Though the method signature doesn't say so explicitly, + * the map returned by {@link #asMap} has {@code Set} values. + * + * @author Jared Levy + */ +@GwtCompatible +public interface SetMultimap extends Multimap { + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + Set get(@Nullable K key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + Set removeAll(@Nullable Object key); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + Set replaceValues(K key, Iterable values); + + /** + * {@inheritDoc} + * + *

Because a {@code SetMultimap} has unique values for a given key, this + * method returns a {@link Set}, instead of the {@link java.util.Collection} + * specified in the {@link Multimap} interface. + */ + Set> entries(); + + /** + * {@inheritDoc} + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link Set} values. + */ + Map> asMap(); + + /** + * Compares the specified object to this multimap for equality. + * + *

Two {@code SetMultimap} instances are equal if, for each key, they + * contain the same values. Equality does not depend on the ordering of keys + * or values. + */ + boolean equals(@Nullable Object obj); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Sets.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Sets.java new file mode 100644 index 00000000000..157671f50a9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Sets.java @@ -0,0 +1,729 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkArgument; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; +import org.elasticsearch.util.gcommon.base.Predicate; +import org.elasticsearch.util.gcommon.base.Predicates; +import org.elasticsearch.util.gcommon.collect.Collections2.FilteredCollection; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.annotation.Nullable; + +/** + * Static utility methods pertaining to {@link Set} instances. Also see this + * class's counterparts {@link Lists} and {@link Maps}. + * + * @author Kevin Bourrillion + * @author Jared Levy + */ +@GwtCompatible +public final class Sets { + private Sets() {} + + /** + * Returns an immutable set instance containing the given enum elements. + * Internally, the returned set will be backed by an {@link EnumSet}. + * + *

The iteration order of the returned set follows the enum's iteration + * order, not the order in which the elements are provided to the method. + * + * @param anElement one of the elements the set should contain + * @param otherElements the rest of the elements the set should contain + * @return an immutable set containing those elements, minus duplicates + */ + // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 + @GwtCompatible(serializable = true) + public static > ImmutableSet immutableEnumSet( + E anElement, E... otherElements) { + return new ImmutableEnumSet(EnumSet.of(anElement, otherElements)); + } + + /** + * Returns an immutable set instance containing the given enum elements. + * Internally, the returned set will be backed by an {@link EnumSet}. + * + *

The iteration order of the returned set follows the enum's iteration + * order, not the order in which the elements appear in the given collection. + * + * @param elements the elements, all of the same {@code enum} type, that the + * set should contain + * @return an immutable set containing those elements, minus duplicates + */ + // http://code.google.com/p/google-web-toolkit/issues/detail?id=3028 + @GwtCompatible(serializable = true) + public static > ImmutableSet immutableEnumSet( + Iterable elements) { + Iterator iterator = elements.iterator(); + if (!iterator.hasNext()) { + return ImmutableSet.of(); + } + if (elements instanceof EnumSet) { + EnumSet enumSetClone = EnumSet.copyOf((EnumSet) elements); + return new ImmutableEnumSet(enumSetClone); + } + E first = iterator.next(); + EnumSet set = EnumSet.of(first); + while (iterator.hasNext()) { + set.add(iterator.next()); + } + return new ImmutableEnumSet(set); + } + + /** + * Returns a new {@code EnumSet} instance containing the given elements. + * Unlike {@link EnumSet#copyOf(Collection)}, this method does not produce an + * exception on an empty collection, and it may be called on any iterable, not + * just a {@code Collection}. + */ + public static > EnumSet newEnumSet(Iterable iterable, + Class elementType) { + /* + * TODO: noneOf() and addAll() will both throw NullPointerExceptions when + * appropriate. However, NullPointerTester will fail on this method because + * it passes in Class.class instead of an enum type. This means that, when + * iterable is null but elementType is not, noneOf() will throw a + * ClassCastException before addAll() has a chance to throw a + * NullPointerException. NullPointerTester considers this a failure. + * Ideally the test would be fixed, but it would require a special case for + * Class where E extends Enum. Until that happens (if ever), leave + * checkNotNull() here. For now, contemplate the irony that checking + * elementType, the problem argument, is harmful, while checking iterable, + * the innocent bystander, is effective. + */ + checkNotNull(iterable); + EnumSet set = EnumSet.noneOf(elementType); + Iterables.addAll(set, iterable); + return set; + } + + // HashSet + + /** + * Creates a mutable, empty {@code HashSet} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableSet#of()} instead. + * + *

Note: if {@code E} is an {@link Enum} type, use {@link + * EnumSet#noneOf} instead. + * + * @return a new, empty {@code HashSet} + */ + public static HashSet newHashSet() { + return new HashSet(); + } + + /** + * Creates a mutable {@code HashSet} instance containing the given + * elements in unspecified order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#of(Object[])} instead. + * + *

Note: if {@code E} is an {@link Enum} type, use {@link + * EnumSet#of(Enum, Enum[])} instead. + * + * @param elements the elements that the set should contain + * @return a new {@code HashSet} containing those elements (minus duplicates) + */ + public static HashSet newHashSet(E... elements) { + int capacity = Maps.capacity(elements.length); + HashSet set = new HashSet(capacity); + Collections.addAll(set, elements); + return set; + } + + /** + * Creates an empty {@code HashSet} instance with enough capacity to hold the + * specified number of elements without rehashing. + * + * @param expectedSize the expected size + * @return a new, empty {@code HashSet} with enough capacity to hold {@code + * expectedSize} elements without rehashing + * @throws IllegalArgumentException if {@code expectedSize} is negative + */ + public static HashSet newHashSetWithExpectedSize(int expectedSize) { + return new HashSet(Maps.capacity(expectedSize)); + } + + /** + * Creates a mutable {@code HashSet} instance containing the given + * elements in unspecified order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#copyOf(Iterable)} instead. + * + *

Note: if {@code E} is an {@link Enum} type, use + * {@link #newEnumSet(Iterable, Class)} instead. + * + * @param elements the elements that the set should contain + * @return a new {@code HashSet} containing those elements (minus duplicates) + */ + public static HashSet newHashSet(Iterable elements) { + if (elements instanceof Collection) { + @SuppressWarnings("unchecked") + Collection collection = (Collection) elements; + return new HashSet(collection); + } else { + return newHashSet(elements.iterator()); + } + } + + /** + * Creates a mutable {@code HashSet} instance containing the given + * elements in unspecified order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#copyOf(Iterable)} instead. + * + *

Note: if {@code E} is an {@link Enum} type, you should create an + * {@link EnumSet} instead. + * + * @param elements the elements that the set should contain + * @return a new {@code HashSet} containing those elements (minus duplicates) + */ + public static HashSet newHashSet(Iterator elements) { + HashSet set = newHashSet(); + while (elements.hasNext()) { + set.add(elements.next()); + } + return set; + } + + // LinkedHashSet + + /** + * Creates a mutable, empty {@code LinkedHashSet} instance. + * + *

Note: if mutability is not required, use {@link + * ImmutableSet#of()} instead. + * + * @return a new, empty {@code LinkedHashSet} + */ + public static LinkedHashSet newLinkedHashSet() { + return new LinkedHashSet(); + } + + /** + * Creates a mutable {@code LinkedHashSet} instance containing the + * given elements in order. + * + *

Note: if mutability is not required and the elements are + * non-null, use {@link ImmutableSet#copyOf(Iterable)} instead. + * + * @param elements the elements that the set should contain, in order + * @return a new {@code LinkedHashSet} containing those elements (minus + * duplicates) + */ + public static LinkedHashSet newLinkedHashSet( + Iterable elements) { + if (elements instanceof Collection) { + @SuppressWarnings("unchecked") + Collection collection = (Collection) elements; + return new LinkedHashSet(collection); + } else { + LinkedHashSet set = newLinkedHashSet(); + for (E element : elements) { + set.add(element); + } + return set; + } + } + + // TreeSet + + /** + * Creates a mutable, empty {@code TreeSet} instance sorted by the + * natural sort ordering of its elements. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedSet#of()} instead. + * + * @return a new, empty {@code TreeSet} + */ + @SuppressWarnings("unchecked") // allow ungenerified Comparable types + public static TreeSet newTreeSet() { + return new TreeSet(); + } + + /** + * Creates a mutable {@code TreeSet} instance containing the given + * elements sorted by their natural ordering. + * + *

Note: if mutability is not required, use {@link + * ImmutableSortedSet#copyOf(Iterable)} instead. + * + *

Note: If {@code elements} is a {@code SortedSet} with an explicit + * comparator, this method has different behavior than + * {@link TreeSet#TreeSet(SortedSet)}, which returns a {@code TreeSet} with + * that comparator. + * + * @param elements the elements that the set should contain + * @return a new {@code TreeSet} containing those elements (minus duplicates) + */ + @SuppressWarnings("unchecked") // allow ungenerified Comparable types + public static TreeSet newTreeSet( + Iterable elements) { + TreeSet set = newTreeSet(); + for (E element : elements) { + set.add(element); + } + return set; + } + + /** + * Creates a mutable, empty {@code TreeSet} instance with the given + * comparator. + * + *

Note: if mutability is not required, use {@code + * ImmutableSortedSet.orderedBy(comparator).build()} instead. + * + * @param comparator the comparator to use to sort the set + * @return a new, empty {@code TreeSet} + * @throws NullPointerException if {@code comparator} is null + */ + public static TreeSet newTreeSet(Comparator comparator) { + return new TreeSet(checkNotNull(comparator)); + } + + /** + * Creates an {@code EnumSet} consisting of all enum values that are not in + * the specified collection. If the collection is an {@link EnumSet}, this + * method has the same behavior as {@link EnumSet#complementOf}. Otherwise, + * the specified collection must contain at least one element, in order to + * determine the element type. If the collection could be empty, use + * {@link #complementOf(Collection, Class)} instead of this method. + * + * @param collection the collection whose complement should be stored in the + * enum set + * @return a new, modifiable {@code EnumSet} containing all values of the enum + * that aren't present in the given collection + * @throws IllegalArgumentException if {@code collection} is not an + * {@code EnumSet} instance and contains no elements + */ + public static > EnumSet complementOf( + Collection collection) { + if (collection instanceof EnumSet) { + return EnumSet.complementOf((EnumSet) collection); + } + checkArgument(!collection.isEmpty(), + "collection is empty; use the other version of this method"); + Class type = collection.iterator().next().getDeclaringClass(); + return makeComplementByHand(collection, type); + } + + /** + * Creates an {@code EnumSet} consisting of all enum values that are not in + * the specified collection. This is equivalent to + * {@link EnumSet#complementOf}, but can act on any input collection, as long + * as the elements are of enum type. + * + * @param collection the collection whose complement should be stored in the + * {@code EnumSet} + * @param type the type of the elements in the set + * @return a new, modifiable {@code EnumSet} initially containing all the + * values of the enum not present in the given collection + */ + public static > EnumSet complementOf( + Collection collection, Class type) { + checkNotNull(collection); + return (collection instanceof EnumSet) + ? EnumSet.complementOf((EnumSet) collection) + : makeComplementByHand(collection, type); + } + + private static > EnumSet makeComplementByHand( + Collection collection, Class type) { + EnumSet result = EnumSet.allOf(type); + result.removeAll(collection); + return result; + } + + /* + * Regarding newSetForMap() and SetFromMap: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ + + /** + * Returns a set backed by the specified map. The resulting set displays + * the same ordering, concurrency, and performance characteristics as the + * backing map. In essence, this factory method provides a {@link Set} + * implementation corresponding to any {@link Map} implementation. There is no + * need to use this method on a {@link Map} implementation that already has a + * corresponding {@link Set} implementation (such as {@link HashMap} or + * {@link TreeMap}). + * + *

Each method invocation on the set returned by this method results in + * exactly one method invocation on the backing map or its keySet + * view, with one exception. The addAll method is implemented as a + * sequence of put invocations on the backing map. + * + *

The specified map must be empty at the time this method is invoked, + * and should not be accessed directly after this method returns. These + * conditions are ensured if the map is created empty, passed directly + * to this method, and no reference to the map is retained, as illustrated + * in the following code fragment:

  {@code
+   *
+   *  Set identityHashSet = Sets.newSetFromMap(
+   *      new IdentityHashMap());}
+   *
+   * This method has the same behavior as the JDK 6 method
+   * {@code Collections.newSetFromMap()}. The returned set is serializable if
+   * the backing map is.
+   *
+   * @param map the backing map
+   * @return the set backed by the map
+   * @throws IllegalArgumentException if map is not empty
+   */
+  public static  Set newSetFromMap(Map map) {
+    return new SetFromMap(map);
+  }
+
+  private static class SetFromMap extends AbstractSet
+      implements Set, Serializable {
+    private final Map m; // The backing map
+    private transient Set s; // Its keySet
+
+    SetFromMap(Map map) {
+      checkArgument(map.isEmpty(), "Map is non-empty");
+      m = map;
+      s = map.keySet();
+    }
+
+    @Override public void clear() {
+      m.clear();
+    }
+    @Override public int size() {
+      return m.size();
+    }
+    @Override public boolean isEmpty() {
+      return m.isEmpty();
+    }
+    @Override public boolean contains(Object o) {
+      return m.containsKey(o);
+    }
+    @Override public boolean remove(Object o) {
+      return m.remove(o) != null;
+    }
+    @Override public boolean add(E e) {
+      return m.put(e, Boolean.TRUE) == null;
+    }
+    @Override public Iterator iterator() {
+      return s.iterator();
+    }
+    @Override public Object[] toArray() {
+      return s.toArray();
+    }
+    @Override public  T[] toArray(T[] a) {
+      return s.toArray(a);
+    }
+    @Override public String toString() {
+      return s.toString();
+    }
+    @Override public int hashCode() {
+      return s.hashCode();
+    }
+    @Override public boolean equals(@Nullable Object object) {
+      return this == object || this.s.equals(object);
+    }
+    @Override public boolean containsAll(Collection c) {
+      return s.containsAll(c);
+    }
+    @Override public boolean removeAll(Collection c) {
+      return s.removeAll(c);
+    }
+    @Override public boolean retainAll(Collection c) {
+      return s.retainAll(c);
+    }
+
+    // addAll is the only inherited implementation
+
+    static final long serialVersionUID = 0;
+
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException {
+      stream.defaultReadObject();
+      s = m.keySet();
+    }
+  }
+
+  /**
+   * An unmodifiable view of a set which may be backed by other sets; this view
+   * will change as the backing sets do. Contains methods to copy the data into
+   * a new set which will then remain stable. There is usually no reason to
+   * retain a reference of type {@code SetView}; typically, you either use it
+   * as a plain {@link Set}, or immediately invoke {@link #immutableCopy} or
+   * {@link #copyInto} and forget the {@code SetView} itself.
+   */
+  public abstract static class SetView extends AbstractSet {
+    private SetView() {} // no subclasses but our own
+
+    /**
+     * Returns an immutable copy of the current contents of this set view.
+     * Does not support null elements.
+     *
+     * 

Warning: this may have unexpected results if a backing set of + * this view uses a nonstandard notion of equivalence, for example if it is + * a {@link TreeSet} using a comparator that is inconsistent with {@link + * Object#equals(Object)}. + */ + public ImmutableSet immutableCopy() { + return ImmutableSet.copyOf(this); + } + + /** + * Copies the current contents of this set view into an existing set. This + * method has equivalent behavior to {@code set.addAll(this)}, assuming that + * all the sets involved are based on the same notion of equivalence. + */ + // Note: S should logically extend Set but can't due to either + // some javac bug or some weirdness in the spec, not sure which. + public > S copyInto(S set) { + set.addAll(this); + return set; + } + } + + /** + * Returns an unmodifiable view of the union of two sets. The returned + * set contains all elements that are contained in either backing set. + * Iterating over the returned set iterates first over all the elements of + * {@code set1}, then over each element of {@code set2}, in order, that is not + * contained in {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based on + * different equivalence relations (as {@link HashSet}, {@link TreeSet}, and + * the {@link Map#keySet} of an {@link IdentityHashMap} all are). + * + *

Note: The returned view performs better when {@code set1} is the + * smaller of the two sets. If you have reason to believe one of your sets + * will generally be smaller than the other, pass it first. + */ + public static SetView union( + final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + // TODO: once we have OrderedIterators, check if these are compatible + // sorted sets and use that instead if so + + final Set set2minus1 = difference(set2, set1); + + return new SetView() { + @Override public int size() { + return set1.size() + set2minus1.size(); + } + @Override public boolean isEmpty() { + return set1.isEmpty() && set2.isEmpty(); + } + @Override public Iterator iterator() { + return Iterators.unmodifiableIterator( + Iterators.concat(set1.iterator(), set2minus1.iterator())); + } + @Override public boolean contains(Object object) { + return set1.contains(object) || set2.contains(object); + } + @Override public > S copyInto(S set) { + set.addAll(set1); + set.addAll(set2); + return set; + } + @Override public ImmutableSet immutableCopy() { + return new ImmutableSet.Builder() + .addAll(set1).addAll(set2).build(); + } + }; + } + + /** + * Returns an unmodifiable view of the intersection of two sets. The + * returned set contains all elements that are contained by both backing sets. + * The iteration order of the returned set matches that of {@code set1}. + * + *

Results are undefined if {@code set1} and {@code set2} are sets based + * on different equivalence relations (as {@code HashSet}, {@code TreeSet}, + * and the keySet of an {@code IdentityHashMap} all are). + * + *

Note: The returned view performs slightly better when {@code + * set1} is the smaller of the two sets. If you have reason to believe one of + * your sets will generally be smaller than the other, pass it first. + * Unfortunately, since this method sets the generic type of the returned set + * based on the type of the first set passed, this could in rare cases force + * you to make a cast, for example:

  {@code
+   *
+   *  Set aFewBadObjects = ...
+   *  Set manyBadStrings = ...
+   *
+   *  // impossible for a non-String to be in the intersection
+   *  SuppressWarnings("unchecked")
+   *  Set badStrings = (Set) Sets.intersection(
+   *      aFewBadObjects, manyBadStrings);}
+   *
+   * This is unfortunate, but should come up only very rarely.
+   */
+  public static  SetView intersection(
+      final Set set1, final Set set2) {
+    checkNotNull(set1, "set1");
+    checkNotNull(set2, "set2");
+
+    // TODO: once we have OrderedIterators, check if these are compatible
+    // sorted sets and use that instead if so
+
+    final Predicate inSet2 = Predicates.in(set2);
+    return new SetView() {
+      @Override public Iterator iterator() {
+        return Iterators.filter(set1.iterator(), inSet2);
+      }
+      @Override public int size() {
+        return Iterators.size(iterator());
+      }
+      @Override public boolean isEmpty() {
+        return !iterator().hasNext();
+      }
+      @Override public boolean contains(Object object) {
+        return set1.contains(object) && set2.contains(object);
+      }
+      @Override public boolean containsAll(Collection collection) {
+        return set1.containsAll(collection)
+            && set2.containsAll(collection);
+      }
+    };
+  }
+
+  /**
+   * Returns an unmodifiable view of the difference of two sets. The
+   * returned set contains all elements that are contained by {@code set1} and
+   * not contained by {@code set2}. {@code set2} may also contain elements not
+   * present in {@code set1}; these are simply ignored. The iteration order of
+   * the returned set matches that of {@code set1}.
+   *
+   * 

Results are undefined if {@code set1} and {@code set2} are sets based + * on different equivalence relations (as {@code HashSet}, {@code TreeSet}, + * and the keySet of an {@code IdentityHashMap} all are). + */ + public static SetView difference( + final Set set1, final Set set2) { + checkNotNull(set1, "set1"); + checkNotNull(set2, "set2"); + + // TODO: once we have OrderedIterators, check if these are compatible + // sorted sets and use that instead if so + + final Predicate notInSet2 = Predicates.not(Predicates.in(set2)); + return new SetView() { + @Override public Iterator iterator() { + return Iterators.filter(set1.iterator(), notInSet2); + } + @Override public int size() { + return Iterators.size(iterator()); + } + @Override public boolean isEmpty() { + return set2.containsAll(set1); + } + @Override public boolean contains(Object element) { + return set1.contains(element) && !set2.contains(element); + } + }; + } + + /** + * Returns the elements of {@code unfiltered} that satisfy a predicate. The + * returned set is a live view of {@code unfiltered}; changes to one affect + * the other. + * + *

The resulting set's iterator does not support {@code remove()}, but all + * other set methods are supported. The set's {@code add()} and + * {@code addAll()} methods throw an {@link IllegalArgumentException} if an + * element that doesn't satisfy the predicate is provided. When methods such + * as {@code removeAll()} and {@code clear()} are called on the filtered set, + * only elements that satisfy the filter will be removed from the underlying + * collection. + * + *

The returned set isn't threadsafe or serializable, even if + * {@code unfiltered} is. + * + *

Many of the filtered set's methods, such as {@code size()}, iterate + * across every element in the underlying set and determine which elements + * satisfy the filter. When a live view is not needed, it may be faster + * to copy {@code Iterables.filter(unfiltered, predicate)} and use the copy. + */ + public static Set filter( + Set unfiltered, Predicate predicate) { + if (unfiltered instanceof FilteredSet) { + // Support clear(), removeAll(), and retainAll() when filtering a filtered + // collection. + FilteredSet filtered = (FilteredSet) unfiltered; + Predicate combinedPredicate + = Predicates.and(filtered.predicate, predicate); + return new FilteredSet( + (Set) filtered.unfiltered, combinedPredicate); + } + + return new FilteredSet( + checkNotNull(unfiltered), checkNotNull(predicate)); + } + + private static class FilteredSet extends FilteredCollection + implements Set { + FilteredSet(Set unfiltered, Predicate predicate) { + super(unfiltered, predicate); + } + + @Override public boolean equals(@Nullable Object object) { + return Collections2.setEquals(this, object); + } + + @Override public int hashCode() { + return hashCodeImpl(this); + } + } + + /** + * Calculates and returns the hash code of {@code s}. + */ + static int hashCodeImpl(Set s) { + int hashCode = 0; + for (Object o : s) { + hashCode += o != null ? o.hashCode() : 0; + } + return hashCode; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableList.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableList.java new file mode 100644 index 00000000000..71a0b34379a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableList.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Preconditions; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableList} with exactly one element. + * + * @author Hayward Chan + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableList extends ImmutableList { + final transient E element; + + SingletonImmutableList(E element) { + this.element = checkNotNull(element); + } + + public E get(int index) { + Preconditions.checkElementIndex(index, 1); + return element; + } + + @Override public int indexOf(@Nullable Object object) { + return element.equals(object) ? 0 : -1; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(element); + } + + @Override public int lastIndexOf(@Nullable Object object) { + return element.equals(object) ? 0 : -1; + } + + public ListIterator listIterator() { + return listIterator(0); + } + + public ListIterator listIterator(final int start) { + // suboptimal but not worth optimizing. + return Collections.singletonList(element).listIterator(start); + } + + public int size() { + return 1; + } + + @Override public ImmutableList subList(int fromIndex, int toIndex) { + Preconditions.checkPositionIndexes(fromIndex, toIndex, 1); + return (fromIndex == toIndex) ? ImmutableList.of() : this; + } + + @Override public boolean contains(@Nullable Object object) { + return element.equals(object); + } + + @Override public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof List) { + List that = (List) object; + return that.size() == 1 && element.equals(that.get(0)); + } + return false; + } + + @Override public int hashCode() { + // not caching hash code since it could change if the element is mutable + // in a way that modifies its hash code. + return 31 + element.hashCode(); + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public Object[] toArray() { + return new Object[] { element }; + } + + @Override public T[] toArray(T[] array) { + if (array.length == 0) { + array = ObjectArrays.newArray(array, 1); + } else if (array.length > 1) { + array[1] = null; + } + // Writes will produce ArrayStoreException when the toArray() doc requires. + Object[] objectArray = array; + objectArray[0] = element; + return array; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableMap.java new file mode 100644 index 00000000000..25aa3dee5a8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableMap.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableMap} with exactly one entry. + * + * @author Jesse Wilson + * @author Kevin Bourrillion + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableMap extends ImmutableMap { + final transient K singleKey; + final transient V singleValue; + + private transient Entry entry; + + SingletonImmutableMap(K singleKey, V singleValue) { + this.singleKey = singleKey; + this.singleValue = singleValue; + } + + SingletonImmutableMap(Entry entry) { + this.entry = entry; + this.singleKey = entry.getKey(); + this.singleValue = entry.getValue(); + } + + private Entry entry() { + Entry e = entry; + return (e == null) + ? (entry = Maps.immutableEntry(singleKey, singleValue)) : e; + } + + @Override public V get(Object key) { + return singleKey.equals(key) ? singleValue : null; + } + + public int size() { + return 1; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean containsKey(Object key) { + return singleKey.equals(key); + } + + @Override public boolean containsValue(Object value) { + return singleValue.equals(value); + } + + private transient ImmutableSet> entrySet; + + @Override public ImmutableSet> entrySet() { + ImmutableSet> es = entrySet; + return (es == null) ? (entrySet = ImmutableSet.of(entry())) : es; + } + + private transient ImmutableSet keySet; + + @Override public ImmutableSet keySet() { + ImmutableSet ks = keySet; + return (ks == null) ? (keySet = ImmutableSet.of(singleKey)) : ks; + } + + private transient ImmutableCollection values; + + @Override public ImmutableCollection values() { + ImmutableCollection v = values; + return (v == null) ? (values = new Values(singleValue)) : v; + } + + @SuppressWarnings("serial") // uses writeReplace(), not default serialization + private static class Values extends ImmutableCollection { + final V singleValue; + + Values(V singleValue) { + this.singleValue = singleValue; + } + + @Override public boolean contains(Object object) { + return singleValue.equals(object); + } + + @Override public boolean isEmpty() { + return false; + } + + public int size() { + return 1; + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(singleValue); + } + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Map) { + Map that = (Map) object; + if (that.size() != 1) { + return false; + } + Entry entry = that.entrySet().iterator().next(); + return singleKey.equals(entry.getKey()) + && singleValue.equals(entry.getValue()); + } + return false; + } + + @Override public int hashCode() { + return singleKey.hashCode() ^ singleValue.hashCode(); + } + + @Override public String toString() { + return new StringBuilder() + .append('{') + .append(singleKey.toString()) + .append('=') + .append(singleValue.toString()) + .append('}') + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableSet.java new file mode 100644 index 00000000000..7adec710bd1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SingletonImmutableSet.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.base.Preconditions; + +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Implementation of {@link ImmutableSet} with exactly one element. + * + * @author Kevin Bourrillion + * @author Nick Kralevich + */ +@GwtCompatible(serializable = true) +@SuppressWarnings("serial") // uses writeReplace(), not default serialization +final class SingletonImmutableSet extends ImmutableSet { + final transient E element; + + // Non-volatile because: + // - Integer is immutable and thus thread-safe; + // - no problems if one thread overwrites the cachedHashCode from another. + private transient Integer cachedHashCode; + + SingletonImmutableSet(E element) { + this.element = Preconditions.checkNotNull(element); + } + + SingletonImmutableSet(E element, int hashCode) { + // Guaranteed to be non-null by the presence of the pre-computed hash code. + this.element = element; + cachedHashCode = hashCode; + } + + public int size() { + return 1; + } + + @Override public boolean isEmpty() { + return false; + } + + @Override public boolean contains(Object target) { + return element.equals(target); + } + + @Override public UnmodifiableIterator iterator() { + return Iterators.singletonIterator(element); + } + + @Override public Object[] toArray() { + return new Object[] { element }; + } + + @SuppressWarnings({"unchecked"}) + @Override public T[] toArray(T[] array) { + if (array.length == 0) { + array = ObjectArrays.newArray(array, 1); + } else if (array.length > 1) { + array[1] = null; + } + // Writes will produce ArrayStoreException when the toArray() doc requires. + Object[] objectArray = array; + objectArray[0] = element; + return array; + } + + @Override public boolean equals(@Nullable Object object) { + if (object == this) { + return true; + } + if (object instanceof Set) { + Set that = (Set) object; + return that.size() == 1 && element.equals(that.iterator().next()); + } + return false; + } + + @Override public final int hashCode() { + Integer code = cachedHashCode; + if (code == null) { + return cachedHashCode = element.hashCode(); + } + return code; + } + + @Override boolean isHashCodeFast() { + return false; + } + + @Override public String toString() { + String elementToString = element.toString(); + return new StringBuilder(elementToString.length() + 2) + .append('[') + .append(elementToString) + .append(']') + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SortedSetMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SortedSetMultimap.java new file mode 100644 index 00000000000..ebf495c1528 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/SortedSetMultimap.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * A {@code SetMultimap} whose set of values for a given key are kept sorted; + * that is, they comprise a {@link SortedSet}. It cannot hold duplicate + * key-value pairs; adding a key-value pair that's already in the multimap has + * no effect. This interface does not specify the ordering of the multimap's + * keys. + * + *

The {@link #get}, {@link #removeAll}, and {@link #replaceValues} methods + * each return a {@link SortedSet} of values, while {@link Multimap#entries()} + * returns a {@link Set} of map entries. Though the method signature doesn't say + * so explicitly, the map returned by {@link #asMap} has {@code SortedSet} + * values. + * + * @author Jared Levy + */ +@GwtCompatible +public interface SortedSetMultimap extends SetMultimap { + /** + * Returns a collection view of all values associated with a key. If no + * mappings in the multimap have the provided key, an empty collection is + * returned. + * + *

Changes to the returned collection will update the underlying multimap, + * and vice versa. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + SortedSet get(@Nullable K key); + + /** + * Removes all values associated with a given key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + */ + SortedSet removeAll(@Nullable Object key); + + /** + * Stores a collection of values with the same key, replacing any existing + * values for that key. + * + *

Because a {@code SortedSetMultimap} has unique sorted values for a given + * key, this method returns a {@link SortedSet}, instead of the + * {@link java.util.Collection} specified in the {@link Multimap} interface. + * + *

Any duplicates in {@code values} will be stored in the multimap once. + */ + SortedSet replaceValues(K key, Iterable values); + + /** + * Returns a map view that associates each key with the corresponding values + * in the multimap. Changes to the returned map, such as element removal, + * will update the underlying multimap. The map never supports + * {@code setValue()} on the map entries, {@code put}, or {@code putAll}. + * + *

The collections returned by {@code asMap().get(Object)} have the same + * behavior as those returned by {@link #get}. + * + *

Though the method signature doesn't say so explicitly, the returned map + * has {@link SortedSet} values. + */ + Map> asMap(); + + /** + * Returns the comparator that orders the multimap values, with a {@code null} + * indicating that natural ordering is used. + */ + Comparator valueComparator(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Synchronized.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Synchronized.java new file mode 100644 index 00000000000..d4dfdb01225 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/Synchronized.java @@ -0,0 +1,1366 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import org.elasticsearch.util.gcommon.annotations.GwtIncompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedSet; + +import javax.annotation.Nullable; + +/** + * Synchronized collection views. The returned synchronized collection views are + * serializable if the backing collection and the mutex are serializable. + * + *

If a {@code null} is passed as the {@code mutex} parameter to any of this + * class's top-level methods or inner class constructors, the created object + * uses itself as the synchronization mutex. + * + *

This class should be used by other collection classes only. + * + * @author Mike Bostock + * @author Jared Levy + */ +@GwtCompatible +final class Synchronized { + private Synchronized() {} + + /** Abstract base class for synchronized views. */ + static class SynchronizedObject implements Serializable { + private final Object delegate; + protected final Object mutex; + + public SynchronizedObject(Object delegate, @Nullable Object mutex) { + this.delegate = checkNotNull(delegate); + this.mutex = (mutex == null) ? this : mutex; + } + + protected Object delegate() { + return delegate; + } + + // No equals and hashCode; see ForwardingObject for details. + + @Override public String toString() { + synchronized (mutex) { + return delegate.toString(); + } + } + + // Serialization invokes writeObject only when it's private. + // The SynchronizedObject subclasses don't need a writeObject method since + // they don't contain any non-transient member variables, while the + // following writeObject() handles the SynchronizedObject members. + + private void writeObject(ObjectOutputStream stream) throws IOException { + synchronized (mutex) { + stream.defaultWriteObject(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) collection backed by the specified + * collection using the specified mutex. In order to guarantee serial access, + * it is critical that all access to the backing collection is + * accomplished through the returned collection. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned collection:

  {@code
+   *
+   *  Collection s = Synchronized.collection(
+   *      new HashSet(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param collection the collection to be wrapped in a synchronized view + * @return a synchronized view of the specified collection + */ + static Collection collection( + Collection collection, @Nullable Object mutex) { + return new SynchronizedCollection(collection, mutex); + } + + /** @see Synchronized#collection */ + static class SynchronizedCollection extends SynchronizedObject + implements Collection { + public SynchronizedCollection( + Collection delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override protected Collection delegate() { + return (Collection) super.delegate(); + } + + public boolean add(E e) { + synchronized (mutex) { + return delegate().add(e); + } + } + + public boolean addAll(Collection c) { + synchronized (mutex) { + return delegate().addAll(c); + } + } + + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + public boolean contains(Object o) { + synchronized (mutex) { + return delegate().contains(o); + } + } + + public boolean containsAll(Collection c) { + synchronized (mutex) { + return delegate().containsAll(c); + } + } + + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + public Iterator iterator() { + return delegate().iterator(); // manually synchronized + } + + public boolean remove(Object o) { + synchronized (mutex) { + return delegate().remove(o); + } + } + + public boolean removeAll(Collection c) { + synchronized (mutex) { + return delegate().removeAll(c); + } + } + + public boolean retainAll(Collection c) { + synchronized (mutex) { + return delegate().retainAll(c); + } + } + + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + public Object[] toArray() { + synchronized (mutex) { + return delegate().toArray(); + } + } + + public T[] toArray(T[] a) { + synchronized (mutex) { + return delegate().toArray(a); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) set backed by the specified set using + * the specified mutex. In order to guarantee serial access, it is critical + * that all access to the backing set is accomplished through the + * returned set. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned set:

  {@code
+   *
+   *  Set s = Synchronized.set(new HashSet(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param set the set to be wrapped in a synchronized view + * @return a synchronized view of the specified set + */ + public static Set set(Set set, @Nullable Object mutex) { + return new SynchronizedSet(set, mutex); + } + + /** @see Synchronized#set */ + static class SynchronizedSet extends SynchronizedCollection + implements Set { + public SynchronizedSet(Set delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override protected Set delegate() { + return (Set) super.delegate(); + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) sorted set backed by the specified + * sorted set using the specified mutex. In order to guarantee serial access, + * it is critical that all access to the backing sorted set is + * accomplished through the returned sorted set. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned sorted set:

  {@code
+   *
+   *  SortedSet s = Synchronized.sortedSet(
+   *      new TreeSet(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param set the sorted set to be wrapped in a synchronized view + * @return a synchronized view of the specified sorted set + */ + static SortedSet sortedSet(SortedSet set, @Nullable Object mutex) { + return new SynchronizedSortedSet(set, mutex); + } + + /** @see Synchronized#sortedSet */ + static class SynchronizedSortedSet extends SynchronizedSet + implements SortedSet { + public SynchronizedSortedSet( + SortedSet delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override protected SortedSet delegate() { + return (SortedSet) super.delegate(); + } + + public Comparator comparator() { + synchronized (mutex) { + return delegate().comparator(); + } + } + + public SortedSet subSet(E fromElement, E toElement) { + synchronized (mutex) { + return sortedSet(delegate().subSet(fromElement, toElement), mutex); + } + } + + public SortedSet headSet(E toElement) { + synchronized (mutex) { + return sortedSet(delegate().headSet(toElement), mutex); + } + } + + public SortedSet tailSet(E fromElement) { + synchronized (mutex) { + return sortedSet(delegate().tailSet(fromElement), mutex); + } + } + + public E first() { + synchronized (mutex) { + return delegate().first(); + } + } + + public E last() { + synchronized (mutex) { + return delegate().last(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) list backed by the specified list + * using the specified mutex. In order to guarantee serial access, it is + * critical that all access to the backing list is accomplished + * through the returned list. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned list:

  {@code
+   *
+   *  List l = Synchronized.list(new ArrayList(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = l.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

The returned list implements {@link RandomAccess} if the specified list + * implements {@code RandomAccess}. + * + * @param list the list to be wrapped in a synchronized view + * @return a synchronized view of the specified list + */ + static List list(List list, @Nullable Object mutex) { + return (list instanceof RandomAccess) + ? new SynchronizedRandomAccessList(list, mutex) + : new SynchronizedList(list, mutex); + } + + /** @see Synchronized#list */ + static class SynchronizedList extends SynchronizedCollection + implements List { + public SynchronizedList(List delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override protected List delegate() { + return (List) super.delegate(); + } + + public void add(int index, E element) { + synchronized (mutex) { + delegate().add(index, element); + } + } + + public boolean addAll(int index, Collection c) { + synchronized (mutex) { + return delegate().addAll(index, c); + } + } + + public E get(int index) { + synchronized (mutex) { + return delegate().get(index); + } + } + + public int indexOf(Object o) { + synchronized (mutex) { + return delegate().indexOf(o); + } + } + + public int lastIndexOf(Object o) { + synchronized (mutex) { + return delegate().lastIndexOf(o); + } + } + + public ListIterator listIterator() { + return delegate().listIterator(); // manually synchronized + } + + public ListIterator listIterator(int index) { + return delegate().listIterator(index); // manually synchronized + } + + public E remove(int index) { + synchronized (mutex) { + return delegate().remove(index); + } + } + + public E set(int index, E element) { + synchronized (mutex) { + return delegate().set(index, element); + } + } + + @GwtIncompatible("List.subList") + public List subList(int fromIndex, int toIndex) { + synchronized (mutex) { + return list(Platform.subList(delegate(), fromIndex, toIndex), mutex); + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + /** @see Synchronized#list */ + static class SynchronizedRandomAccessList extends SynchronizedList + implements RandomAccess { + public SynchronizedRandomAccessList(List list, @Nullable Object mutex) { + super(list, mutex); + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) multiset backed by the specified + * multiset using the specified mutex. In order to guarantee serial access, it + * is critical that all access to the backing multiset is accomplished + * through the returned multiset. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned multiset:

  {@code
+   *
+   *  Multiset s = Synchronized.multiset(
+   *      HashMultiset.create(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param multiset the multiset to be wrapped + * @return a synchronized view of the specified multiset + */ + private static Multiset multiset( + Multiset multiset, @Nullable Object mutex) { + return new SynchronizedMultiset(multiset, mutex); + } + + /** @see Synchronized#multiset */ + static class SynchronizedMultiset extends SynchronizedCollection + implements Multiset { + private transient Set elementSet; + private transient Set> entrySet; + + public SynchronizedMultiset(Multiset delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override protected Multiset delegate() { + return (Multiset) super.delegate(); + } + + public int count(Object o) { + synchronized (mutex) { + return delegate().count(o); + } + } + + public int add(E e, int n) { + synchronized (mutex) { + return delegate().add(e, n); + } + } + + public int remove(Object o, int n) { + synchronized (mutex) { + return delegate().remove(o, n); + } + } + + public int setCount(E element, int count) { + synchronized (mutex) { + return delegate().setCount(element, count); + } + } + + public boolean setCount(E element, int oldCount, int newCount) { + synchronized (mutex) { + return delegate().setCount(element, oldCount, newCount); + } + } + + public Set elementSet() { + synchronized (mutex) { + if (elementSet == null) { + elementSet = typePreservingSet(delegate().elementSet(), mutex); + } + return elementSet; + } + } + + public Set> entrySet() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = typePreservingSet(delegate().entrySet(), mutex); + } + return entrySet; + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) multimap backed by the specified + * multimap using the specified mutex. In order to guarantee serial access, it + * is critical that all access to the backing multimap is accomplished + * through the returned multimap. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when accessing any of the return multimap's collection views: + *

  {@code
+   *
+   *  Multimap m = Synchronized.multimap(
+   *      HashMultimap.create(), mutex);
+   *  ...
+   *  Set s = m.keySet();  // Needn't be in synchronized block
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static Multimap multimap( + Multimap multimap, @Nullable Object mutex) { + return new SynchronizedMultimap(multimap, mutex); + } + + /** @see Synchronized#multimap */ + private static class SynchronizedMultimap extends SynchronizedObject + implements Multimap { + transient Set keySet; + transient Collection valuesCollection; + transient Collection> entries; + transient Map> asMap; + transient Multiset keys; + + @SuppressWarnings("unchecked") + @Override protected Multimap delegate() { + return (Multimap) super.delegate(); + } + + SynchronizedMultimap(Multimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + public boolean containsKey(Object key) { + synchronized (mutex) { + return delegate().containsKey(key); + } + } + + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + public boolean containsEntry(Object key, Object value) { + synchronized (mutex) { + return delegate().containsEntry(key, value); + } + } + + public Collection get(K key) { + synchronized (mutex) { + return typePreservingCollection(delegate().get(key), mutex); + } + } + + public boolean put(K key, V value) { + synchronized (mutex) { + return delegate().put(key, value); + } + } + + public boolean putAll(K key, Iterable values) { + synchronized (mutex) { + return delegate().putAll(key, values); + } + } + + public boolean putAll(Multimap multimap) { + synchronized (mutex) { + return delegate().putAll(multimap); + } + } + + public Collection replaceValues(K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + + public boolean remove(Object key, Object value) { + synchronized (mutex) { + return delegate().remove(key, value); + } + } + + public Collection removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + public Set keySet() { + synchronized (mutex) { + if (keySet == null) { + keySet = typePreservingSet(delegate().keySet(), mutex); + } + return keySet; + } + } + + public Collection values() { + synchronized (mutex) { + if (valuesCollection == null) { + valuesCollection = collection(delegate().values(), mutex); + } + return valuesCollection; + } + } + + public Collection> entries() { + synchronized (mutex) { + if (entries == null) { + entries = typePreservingCollection(delegate().entries(), mutex); + } + return entries; + } + } + + public Map> asMap() { + synchronized (mutex) { + if (asMap == null) { + asMap = new SynchronizedAsMap(delegate().asMap(), mutex); + } + return asMap; + } + } + + public Multiset keys() { + synchronized (mutex) { + if (keys == null) { + keys = multiset(delegate().keys(), mutex); + } + return keys; + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) list multimap backed by the specified + * multimap using the specified mutex. + * + *

You must follow the warnings described for {@link #multimap}. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static ListMultimap listMultimap( + ListMultimap multimap, @Nullable Object mutex) { + return new SynchronizedListMultimap(multimap, mutex); + } + + /** @see Synchronized#listMultimap */ + private static class SynchronizedListMultimap + extends SynchronizedMultimap implements ListMultimap { + SynchronizedListMultimap( + ListMultimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + @Override protected ListMultimap delegate() { + return (ListMultimap) super.delegate(); + } + @Override public List get(K key) { + synchronized (mutex) { + return list(delegate().get(key), mutex); + } + } + @Override public List removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + @Override public List replaceValues( + K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) set multimap backed by the specified + * multimap using the specified mutex. + * + *

You must follow the warnings described for {@link #multimap}. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static SetMultimap setMultimap( + SetMultimap multimap, @Nullable Object mutex) { + return new SynchronizedSetMultimap(multimap, mutex); + } + + /** @see Synchronized#setMultimap */ + private static class SynchronizedSetMultimap + extends SynchronizedMultimap implements SetMultimap { + transient Set> entrySet; + SynchronizedSetMultimap( + SetMultimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + @Override protected SetMultimap delegate() { + return (SetMultimap) super.delegate(); + } + @Override public Set get(K key) { + synchronized (mutex) { + return set(delegate().get(key), mutex); + } + } + @Override public Set removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + @Override public Set replaceValues( + K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + @Override public Set> entries() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = set(delegate().entries(), mutex); + } + return entrySet; + } + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) sorted set multimap backed by the + * specified multimap using the specified mutex. + * + *

You must follow the warnings described for {@link #multimap}. + * + * @param multimap the multimap to be wrapped in a synchronized view + * @return a synchronized view of the specified multimap + */ + public static SortedSetMultimap sortedSetMultimap( + SortedSetMultimap multimap, @Nullable Object mutex) { + return new SynchronizedSortedSetMultimap(multimap, mutex); + } + + /** @see Synchronized#sortedSetMultimap */ + private static class SynchronizedSortedSetMultimap + extends SynchronizedSetMultimap implements SortedSetMultimap { + SynchronizedSortedSetMultimap( + SortedSetMultimap delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + @Override protected SortedSetMultimap delegate() { + return (SortedSetMultimap) super.delegate(); + } + @Override public SortedSet get(K key) { + synchronized (mutex) { + return sortedSet(delegate().get(key), mutex); + } + } + @Override public SortedSet removeAll(Object key) { + synchronized (mutex) { + return delegate().removeAll(key); // copy not synchronized + } + } + @Override public SortedSet replaceValues( + K key, Iterable values) { + synchronized (mutex) { + return delegate().replaceValues(key, values); // copy not synchronized + } + } + public Comparator valueComparator() { + synchronized (mutex) { + return delegate().valueComparator(); + } + } + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) collection backed by the specified + * collection using the specified mutex. In order to guarantee serial access, + * it is critical that all access to the backing collection is + * accomplished through the returned collection. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned collection:

  {@code
+   *
+   *  Collection s = Synchronized.typePreservingCollection(
+   *      new HashSet(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

If the specified collection is a {@code SortedSet}, {@code Set} or + * {@code List}, this method will behave identically to {@link #sortedSet}, + * {@link #set} or {@link #list} respectively, in that order of specificity. + * + * @param collection the collection to be wrapped in a synchronized view + * @return a synchronized view of the specified collection + */ + private static Collection typePreservingCollection( + Collection collection, @Nullable Object mutex) { + if (collection instanceof SortedSet) { + return sortedSet((SortedSet) collection, mutex); + } else if (collection instanceof Set) { + return set((Set) collection, mutex); + } else if (collection instanceof List) { + return list((List) collection, mutex); + } else { + return collection(collection, mutex); + } + } + + /** + * Returns a synchronized (thread-safe) set backed by the specified set using + * the specified mutex. In order to guarantee serial access, it is critical + * that all access to the backing collection is accomplished through + * the returned collection. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when iterating over the returned collection:

  {@code
+   *
+   *  Set s = Synchronized.typePreservingSet(
+   *      new HashSet(), mutex);
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + *

If the specified collection is a {@code SortedSet} this method will + * behave identically to {@link #sortedSet}. + * + * @param set the set to be wrapped in a synchronized view + * @return a synchronized view of the specified set + */ + public static Set typePreservingSet( + Set set, @Nullable Object mutex) { + if (set instanceof SortedSet) { + return sortedSet((SortedSet) set, mutex); + } else { + return set(set, mutex); + } + } + + /** @see Synchronized#multimap */ + static class SynchronizedAsMapEntries + extends SynchronizedSet>> { + public SynchronizedAsMapEntries( + Set>> delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override public Iterator>> iterator() { + // Must be manually synchronized. + final Iterator>> iterator = super.iterator(); + return new ForwardingIterator>>() { + @Override protected Iterator>> delegate() { + return iterator; + } + + @Override public Map.Entry> next() { + final Map.Entry> entry = iterator.next(); + return new ForwardingMapEntry>() { + @Override protected Map.Entry> delegate() { + return entry; + } + @Override public Collection getValue() { + return typePreservingCollection(entry.getValue(), mutex); + } + }; + } + }; + } + + // See Collections.CheckedMap.CheckedEntrySet for details on attacks. + + @Override public Object[] toArray() { + synchronized (mutex) { + return ObjectArrays.toArrayImpl(delegate()); + } + } + @Override public T[] toArray(T[] array) { + synchronized (mutex) { + return ObjectArrays.toArrayImpl(delegate(), array); + } + } + @Override public boolean contains(Object o) { + synchronized (mutex) { + return Maps.containsEntryImpl(delegate(), o); + } + } + @Override public boolean containsAll(Collection c) { + synchronized (mutex) { + return Collections2.containsAll(delegate(), c); + } + } + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return Collections2.setEquals(delegate(), o); + } + } + @Override public boolean remove(Object o) { + synchronized (mutex) { + return Maps.removeEntryImpl(delegate(), o); + } + } + @Override public boolean removeAll(Collection c) { + synchronized (mutex) { + return Iterators.removeAll(delegate().iterator(), c); + } + } + @Override public boolean retainAll(Collection c) { + synchronized (mutex) { + return Iterators.retainAll(delegate().iterator(), c); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) map backed by the specified map using + * the specified mutex. In order to guarantee serial access, it is critical + * that all access to the backing map is accomplished through the + * returned map. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when accessing any of the return map's collection views: + *

  {@code
+   *
+   *  Map m = Synchronized.map(
+   *      new HashMap(), mutex);
+   *  ...
+   *  Set s = m.keySet();  // Needn't be in synchronized block
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param map the map to be wrapped in a synchronized view + * @return a synchronized view of the specified map + */ + public static Map map(Map map, @Nullable Object mutex) { + return new SynchronizedMap(map, mutex); + } + + /** @see Synchronized#map */ + static class SynchronizedMap extends SynchronizedObject + implements Map { + private transient Set keySet; + private transient Collection values; + private transient Set> entrySet; + + public SynchronizedMap(Map delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @SuppressWarnings("unchecked") + @Override protected Map delegate() { + return (Map) super.delegate(); + } + + public void clear() { + synchronized (mutex) { + delegate().clear(); + } + } + + public boolean containsKey(Object key) { + synchronized (mutex) { + return delegate().containsKey(key); + } + } + + public boolean containsValue(Object value) { + synchronized (mutex) { + return delegate().containsValue(value); + } + } + + public Set> entrySet() { + synchronized (mutex) { + if (entrySet == null) { + entrySet = set(delegate().entrySet(), mutex); + } + return entrySet; + } + } + + public V get(Object key) { + synchronized (mutex) { + return delegate().get(key); + } + } + + public boolean isEmpty() { + synchronized (mutex) { + return delegate().isEmpty(); + } + } + + public Set keySet() { + synchronized (mutex) { + if (keySet == null) { + keySet = set(delegate().keySet(), mutex); + } + return keySet; + } + } + + public V put(K key, V value) { + synchronized (mutex) { + return delegate().put(key, value); + } + } + + public void putAll(Map map) { + synchronized (mutex) { + delegate().putAll(map); + } + } + + public V remove(Object key) { + synchronized (mutex) { + return delegate().remove(key); + } + } + + public int size() { + synchronized (mutex) { + return delegate().size(); + } + } + + public Collection values() { + synchronized (mutex) { + if (values == null) { + values = collection(delegate().values(), mutex); + } + return values; + } + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + synchronized (mutex) { + return delegate().equals(o); + } + } + + @Override public int hashCode() { + synchronized (mutex) { + return delegate().hashCode(); + } + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a synchronized (thread-safe) bimap backed by the specified bimap + * using the specified mutex. In order to guarantee serial access, it is + * critical that all access to the backing bimap is accomplished + * through the returned bimap. + * + *

It is imperative that the user manually synchronize on the specified + * mutex when accessing any of the return bimap's collection views: + *

  {@code
+   *
+   *  BiMap m = Synchronized.biMap(
+   *      HashBiMap.create(), mutex);
+   *  ...
+   *  Set s = m.keySet();  // Needn't be in synchronized block
+   *  ...
+   *  synchronized (mutex) {
+   *    Iterator i = s.iterator(); // Must be in synchronized block
+   *    while (i.hasNext()) {
+   *      foo(i.next());
+   *    }
+   *  }}
+ * + * Failure to follow this advice may result in non-deterministic behavior. + * + * @param bimap the bimap to be wrapped in a synchronized view + * @return a synchronized view of the specified bimap + */ + public static BiMap biMap( + BiMap bimap, @Nullable Object mutex) { + return new SynchronizedBiMap(bimap, mutex, null); + } + + /** @see Synchronized#biMap */ + static class SynchronizedBiMap extends SynchronizedMap + implements BiMap, Serializable { + private transient Set valueSet; + private transient BiMap inverse; + + public SynchronizedBiMap( + BiMap delegate, @Nullable Object mutex, + @Nullable BiMap inverse) { + super(delegate, mutex); + this.inverse = inverse; + } + + @Override protected BiMap delegate() { + return (BiMap) super.delegate(); + } + + @Override public Set values() { + synchronized (mutex) { + if (valueSet == null) { + valueSet = set(delegate().values(), mutex); + } + return valueSet; + } + } + + public V forcePut(K key, V value) { + synchronized (mutex) { + return delegate().forcePut(key, value); + } + } + + public BiMap inverse() { + synchronized (mutex) { + if (inverse == null) { + inverse + = new SynchronizedBiMap(delegate().inverse(), mutex, this); + } + return inverse; + } + } + + private static final long serialVersionUID = 0; + } + + /** @see SynchronizedMultimap#asMap */ + static class SynchronizedAsMap + extends SynchronizedMap> { + private transient Set>> asMapEntrySet; + private transient Collection> asMapValues; + + public SynchronizedAsMap( + Map> delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override public Collection get(Object key) { + synchronized (mutex) { + Collection collection = super.get(key); + return (collection == null) ? null + : typePreservingCollection(collection, mutex); + } + } + + @Override public Set>> entrySet() { + synchronized (mutex) { + if (asMapEntrySet == null) { + asMapEntrySet = new SynchronizedAsMapEntries( + delegate().entrySet(), mutex); + } + return asMapEntrySet; + } + } + + @Override public Collection> values() { + synchronized (mutex) { + if (asMapValues == null) { + asMapValues + = new SynchronizedAsMapValues(delegate().values(), mutex); + } + return asMapValues; + } + } + + @Override public boolean containsValue(Object o) { + // values() and its contains() method are both synchronized. + return values().contains(o); + } + + private static final long serialVersionUID = 0; + } + + /** @see SynchronizedMultimap#asMap */ + static class SynchronizedAsMapValues + extends SynchronizedCollection> { + SynchronizedAsMapValues( + Collection> delegate, @Nullable Object mutex) { + super(delegate, mutex); + } + + @Override public Iterator> iterator() { + // Must be manually synchronized. + final Iterator> iterator = super.iterator(); + return new ForwardingIterator>() { + @Override protected Iterator> delegate() { + return iterator; + } + @Override public Collection next() { + return typePreservingCollection(iterator.next(), mutex); + } + }; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/TreeMultimap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/TreeMultimap.java new file mode 100644 index 00000000000..4e6f9bc9550 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/TreeMultimap.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; +import static org.elasticsearch.util.gcommon.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; +import java.util.Comparator; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.annotation.Nullable; + +/** + * Implementation of {@code Multimap} whose keys and values are ordered by + * their natural ordering or by supplied comparators. In all cases, this + * implementation uses {@link Comparable#compareTo} or {@link + * Comparator#compare} instead of {@link Object#equals} to determine + * equivalence of instances. + * + *

Warning: The comparators or comparables used must be consistent + * with equals as explained by the {@link Comparable} class specification. + * Otherwise, the resulting multiset will violate the general contract of {@link + * SetMultimap}, which it is specified in terms of {@link Object#equals}. + * + *

The collections returned by {@code keySet} and {@code asMap} iterate + * through the keys according to the key comparator ordering or the natural + * ordering of the keys. Similarly, {@code get}, {@code removeAll}, and {@code + * replaceValues} return collections that iterate through the values according + * to the value comparator ordering or the natural ordering of the values. The + * collections generated by {@code entries}, {@code keys}, and {@code values} + * iterate across the keys according to the above key ordering, and for each + * key they iterate across the values according to the value ordering. + * + *

The multimap does not store duplicate key-value pairs. Adding a new + * key-value pair equal to an existing key-value pair has no effect. + * + *

Depending on the comparators, null keys and values may or may not be + * supported. The natural ordering does not support nulls. All optional multimap + * methods are supported, and all returned views are modifiable. + * + *

This class is not threadsafe when any concurrent operations update the + * multimap. Concurrent read operations will work correctly. To allow concurrent + * update operations, wrap your multimap with a call to {@link + * Multimaps#synchronizedSortedSetMultimap}. + * + * @author Jared Levy + */ +@GwtCompatible(serializable = true) +public class TreeMultimap extends AbstractSortedSetMultimap { + private transient Comparator keyComparator; + private transient Comparator valueComparator; + + /** + * Creates an empty {@code TreeMultimap} ordered by the natural ordering of + * its keys and values. + */ + @SuppressWarnings("unchecked") // eclipse doesn't like the raw Comparable + public static + TreeMultimap create() { + return new TreeMultimap(Ordering.natural(), Ordering.natural()); + } + + /** + * Creates an empty {@code TreeMultimap} instance using explicit comparators. + * Neither comparator may be null; use {@link Ordering#natural()} to specify + * natural order. + * + * @param keyComparator the comparator that determines the key ordering + * @param valueComparator the comparator that determines the value ordering + */ + public static TreeMultimap create( + Comparator keyComparator, + Comparator valueComparator) { + return new TreeMultimap(checkNotNull(keyComparator), + checkNotNull(valueComparator)); + } + + /** + * Constructs a {@code TreeMultimap}, ordered by the natural ordering of its + * keys and values, with the same mappings as the specified multimap. + * + * @param multimap the multimap whose contents are copied to this multimap + */ + @SuppressWarnings("unchecked") // eclipse doesn't like the raw Comparable + public static + TreeMultimap create(Multimap multimap) { + return new TreeMultimap(Ordering.natural(), Ordering.natural(), + multimap); + } + + // Used by the TreeMultimap serialization test. + TreeMultimap() { + this(null, null); + } + + // Must be package-private so multimaps with null comparators can be + // serialized (which can be created with Multimaps.newTreeMultimap()). Once + // that method is removed, this constructor can be made private. + TreeMultimap(@Nullable Comparator keyComparator, + @Nullable Comparator valueComparator) { + super((keyComparator == null) + ? new TreeMap>() + : new TreeMap>(keyComparator)); + this.keyComparator = keyComparator; + this.valueComparator = valueComparator; + } + + private TreeMultimap(Comparator keyComparator, + Comparator valueComparator, + Multimap multimap) { + this(keyComparator, valueComparator); + putAll(multimap); + } + + /** + * {@inheritDoc} + * + *

Creates an empty {@code TreeSet} for a collection of values for one key. + * + * @return a new {@code TreeSet} containing a collection of values for one + * key + */ + @Override SortedSet createCollection() { + return (valueComparator == null) + ? new TreeSet() : new TreeSet(valueComparator); + } + + /** + * Returns the comparator that orders the multimap keys. + */ + public Comparator keyComparator() { + return keyComparator; + } + + public Comparator valueComparator() { + return valueComparator; + } + + /** + * {@inheritDoc} + * + *

Because a {@code TreeMultimap} has unique sorted keys, this method + * returns a {@link SortedSet}, instead of the {@link Set} specified in the + * {@link Multimap} interface. + */ + @Override public SortedSet keySet() { + return (SortedSet) super.keySet(); + } + + /** + * {@inheritDoc} + * + *

Because a {@code TreeMultimap} has unique sorted keys, this method + * returns a {@link SortedMap}, instead of the {@link java.util.Map} specified + * in the {@link Multimap} interface. + */ + @Override public SortedMap> asMap() { + return (SortedMap>) super.asMap(); + } + + /** + * @serialData key comparator, value comparator, number of distinct keys, and + * then for each distinct key: the key, number of values for that key, and + * key values + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(keyComparator()); + stream.writeObject(valueComparator()); + Serialization.writeMultimap(this, stream); + } + + @SuppressWarnings("unchecked") // reading data stored by writeObject + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + keyComparator = (Comparator) stream.readObject(); + valueComparator = (Comparator) stream.readObject(); + setMap(new TreeMap>(keyComparator)); + Serialization.populateMultimap(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/TreeMultiset.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/TreeMultiset.java new file mode 100644 index 00000000000..e529bfd50f9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/TreeMultiset.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Comparator; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nullable; + +/** + * A multiset which maintains the ordering of its elements, according to either + * their natural order or an explicit {@link Comparator}. In all cases, this + * implementation uses {@link Comparable#compareTo} or {@link + * Comparator#compare} instead of {@link Object#equals} to determine + * equivalence of instances. + * + *

Warning: The comparison must be consistent with equals as + * explained by the {@link Comparable} class specification. Otherwise, the + * resulting multiset will violate the {@link Collection} contract, which it is + * specified in terms of {@link Object#equals}. + * + * @author Neal Kanodia + * @author Jared Levy + */ +@GwtCompatible +@SuppressWarnings("serial") // we're overriding default serialization +public final class TreeMultiset extends AbstractMapBasedMultiset { + + /** + * Creates a new, empty multiset, sorted according to the elements' natural + * order. All elements inserted into the multiset must implement the + * {@code Comparable} interface. Furthermore, all such elements must be + * mutually comparable: {@code e1.compareTo(e2)} must not throw a + * {@code ClassCastException} for any elements {@code e1} and {@code e2} in + * the multiset. If the user attempts to add an element to the multiset that + * violates this constraint (for example, the user attempts to add a string + * element to a set whose elements are integers), the {@code add(Object)} + * call will throw a {@code ClassCastException}. + * + *

The type specification is {@code }, instead of the + * more specific {@code >}, to support + * classes defined without generics. + */ + @SuppressWarnings("unchecked") // eclipse doesn't like the raw Comparable + public static TreeMultiset create() { + return new TreeMultiset(); + } + + /** + * Creates a new, empty multiset, sorted according to the specified + * comparator. All elements inserted into the multiset must be mutually + * comparable by the specified comparator: {@code comparator.compare(e1, + * e2)} must not throw a {@code ClassCastException} for any elements {@code + * e1} and {@code e2} in the multiset. If the user attempts to add an element + * to the multiset that violates this constraint, the {@code add(Object)} call + * will throw a {@code ClassCastException}. + * + * @param comparator the comparator that will be used to sort this multiset. A + * null value indicates that the elements' natural ordering should + * be used. + */ + public static TreeMultiset create(Comparator comparator) { + return new TreeMultiset(comparator); + } + + /** + * Creates an empty multiset containing the given initial elements, sorted + * according to the elements' natural order. + * + *

The type specification is {@code }, instead of the + * more specific {@code >}, to support + * classes defined without generics. + */ + @SuppressWarnings("unchecked") // eclipse doesn't like the raw Comparable + public static TreeMultiset create( + Iterable elements) { + TreeMultiset multiset = create(); + Iterables.addAll(multiset, elements); + return multiset; + } + + private TreeMultiset() { + super(new TreeMap()); + } + + private TreeMultiset(Comparator comparator) { + super(new TreeMap(comparator)); + } + + /** + * {@inheritDoc} + * + *

In {@code TreeMultiset}, the return type of this method is narrowed + * from {@link Set} to {@link SortedSet}. + */ + @Override public SortedSet elementSet() { + return (SortedSet) super.elementSet(); + } + + @Override public int count(@Nullable Object element) { + try { + return super.count(element); + } catch (NullPointerException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + @Override Set createElementSet() { + return new SortedMapBasedElementSet( + (SortedMap) backingMap()); + } + + private class SortedMapBasedElementSet extends MapBasedElementSet + implements SortedSet { + + SortedMapBasedElementSet(SortedMap map) { + super(map); + } + + SortedMap sortedMap() { + return (SortedMap) getMap(); + } + + public Comparator comparator() { + return sortedMap().comparator(); + } + + public E first() { + return sortedMap().firstKey(); + } + + public E last() { + return sortedMap().lastKey(); + } + + public SortedSet headSet(E toElement) { + return new SortedMapBasedElementSet(sortedMap().headMap(toElement)); + } + + public SortedSet subSet(E fromElement, E toElement) { + return new SortedMapBasedElementSet( + sortedMap().subMap(fromElement, toElement)); + } + + public SortedSet tailSet(E fromElement) { + return new SortedMapBasedElementSet(sortedMap().tailMap(fromElement)); + } + + @Override public boolean remove(Object element) { + try { + return super.remove(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + } + + /* + * TODO: Decide whether entrySet() should return entries with an equals() + * method that calls the comparator to compare the two keys. If that change + * is made, AbstractMultiset.equals() can simply check whether two multisets + * have equal entry sets. + */ + + /** + * @serialData the comparator, the number of distinct elements, the first + * element, its count, the second element, its count, and so on + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + stream.writeObject(elementSet().comparator()); + Serialization.writeMultiset(this, stream); + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + @SuppressWarnings("unchecked") // reading data stored by writeObject + Comparator comparator + = (Comparator) stream.readObject(); + setBackingMap(new TreeMap(comparator)); + Serialization.populateMultiset(this, stream); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/UnmodifiableIterator.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/UnmodifiableIterator.java new file mode 100644 index 00000000000..eadfdc036ed --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/UnmodifiableIterator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.util.Iterator; + +/** + * An iterator that does not support {@link #remove}. + * + * @author Jared Levy + */ +@GwtCompatible +public abstract class UnmodifiableIterator implements Iterator { + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/UsingToStringOrdering.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/UsingToStringOrdering.java new file mode 100644 index 00000000000..0fe4a1a44ee --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/UsingToStringOrdering.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.gcommon.collect; + +import org.elasticsearch.util.gcommon.annotations.GwtCompatible; + +import java.io.Serializable; + +/** An ordering that uses the reverse of the natural order of the values. */ +@GwtCompatible(serializable = true) +final class UsingToStringOrdering + extends Ordering implements Serializable { + static final UsingToStringOrdering INSTANCE = new UsingToStringOrdering(); + + public int compare(Object left, Object right) { + return left.toString().compareTo(right.toString()); + } + + // preserve singleton-ness, so equals() and hashCode() work correctly + private Object readResolve() { + return INSTANCE; + } + + @Override public String toString() { + return "Ordering.usingToString()"; + } + + private UsingToStringOrdering() {} + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/package-info.java new file mode 100644 index 00000000000..d1b3b02db2d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/gcommon/collect/package-info.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +/** + * This package contains generic collection interfaces and implementations, and + * other utilities for working with collections. + * + *

Collection Types

+ * + *
+ *
{@link org.elasticsearch.util.gcommon.collect.Multimap} + *
A new type, which is similar to {@link java.util.Map}, but may contain + * multiple entries with the same key. Some behaviors of + * {@link org.elasticsearch.util.gcommon.collect.Multimap} are left unspecified and are + * provided only by the subtypes mentioned below. + * + *
{@link org.elasticsearch.util.gcommon.collect.SetMultimap} + *
An extension of {@link org.elasticsearch.util.gcommon.collect.Multimap} which has + * order-independent equality and does not allow duplicate entries; that is, + * while a key may appear twice in a {@code SetMultimap}, each must map to a + * different value. {@code SetMultimap} takes its name from the fact that + * the {@linkplain org.elasticsearch.util.gcommon.collect.SetMultimap#get collection of + * values} associated with a given key fulfills the {@link java.util.Set} + * contract. + * + *
{@link org.elasticsearch.util.gcommon.collect.ListMultimap} + *
An extension of {@link org.elasticsearch.util.gcommon.collect.Multimap} which permits + * duplicate entries, supports random access of values for a particular key, + * and has partially order-dependent equality as defined by + * {@link org.elasticsearch.util.gcommon.collect.ListMultimap#equals(Object)}. {@code + * ListMultimap} takes its name from the fact that the {@linkplain + * org.elasticsearch.util.gcommon.collect.ListMultimap#get collection of values} + * associated with a given key fulfills the {@link java.util.List} contract. + * + *
{@link org.elasticsearch.util.gcommon.collect.SortedSetMultimap} + *
An extension of {@link org.elasticsearch.util.gcommon.collect.SetMultimap} for which + * the {@linkplain org.elasticsearch.util.gcommon.collect.SortedSetMultimap#get + * collection values} associated with a given key is a + * {@link java.util.SortedSet}. + * + *
{@link org.elasticsearch.util.gcommon.collect.Multiset} + *
An extension of {@link java.util.Collection} that may contain duplicate + * values like a {@link java.util.List}, yet has order-independent equality + * like a {@link java.util.Set}. One typical use for a multiset is to + * represent a histogram. + * + *
{@link org.elasticsearch.util.gcommon.collect.BiMap} + *
An extension of {@link java.util.Map} that guarantees the uniqueness of + * its values as well as that of its keys. This is sometimes called an + * "invertible map," since the restriction on values enables it to support + * an {@linkplain org.elasticsearch.util.gcommon.collect.BiMap#inverse inverse view} -- + * which is another instance of {@code BiMap}. + * + *
{@link org.elasticsearch.util.gcommon.collect.ClassToInstanceMap} + *
An extension of {@link java.util.Map} that associates a raw type with an + * instance of that type. + *
+ * + *

Collection Implementations

+ * + *

of {@link java.util.List}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableList} + * + * + *

of {@link java.util.Set}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableSet} + * + * + *

of {@link java.util.SortedSet}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableSortedSet} + *
+ * + *

of {@link java.util.Map}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableMap} + *
{@link org.elasticsearch.util.gcommon.collect.MapMaker} (produced by) + * + * + *

of {@link java.util.SortedMap}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableSortedMap} + *
+ * + *

of {@link org.elasticsearch.util.gcommon.collect.Multimap}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.Multimaps#newMultimap} + *
+ * + *

of {@link org.elasticsearch.util.gcommon.collect.ListMultimap}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableListMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.ArrayListMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.LinkedListMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.Multimaps#newListMultimap} + *
+ * + *

of {@link org.elasticsearch.util.gcommon.collect.SetMultimap}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableSetMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.HashMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.LinkedHashMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.TreeMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.Multimaps#newSetMultimap} + *
{@link org.elasticsearch.util.gcommon.collect.Multimaps#newSortedSetMultimap} + *
+ * + *

of {@link org.elasticsearch.util.gcommon.collect.Multiset}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableMultiset} + *
{@link org.elasticsearch.util.gcommon.collect.ConcurrentHashMultiset} + *
{@link org.elasticsearch.util.gcommon.collect.HashMultiset} + *
{@link org.elasticsearch.util.gcommon.collect.LinkedHashMultiset} + *
{@link org.elasticsearch.util.gcommon.collect.TreeMultiset} + *
{@link org.elasticsearch.util.gcommon.collect.EnumMultiset} + *
+ * + *

of {@link org.elasticsearch.util.gcommon.collect.BiMap}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.HashBiMap} + *
{@link org.elasticsearch.util.gcommon.collect.EnumBiMap} + *
{@link org.elasticsearch.util.gcommon.collect.EnumHashBiMap} + *
+ * + *

of {@link org.elasticsearch.util.gcommon.collect.ClassToInstanceMap}

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.ImmutableClassToInstanceMap} + *
{@link org.elasticsearch.util.gcommon.collect.MutableClassToInstanceMap} + *
+ * + *

Skeletal implementations

+ *
+ *
{@link org.elasticsearch.util.gcommon.collect.AbstractIterator} + *
{@link org.elasticsearch.util.gcommon.collect.UnmodifiableIterator} + *
+ * + *

Utilities

+ * + *
+ *
{@link org.elasticsearch.util.gcommon.collect.Collections2} + *
{@link org.elasticsearch.util.gcommon.collect.Iterators} + *
{@link org.elasticsearch.util.gcommon.collect.Iterables} + *
{@link org.elasticsearch.util.gcommon.collect.Lists} + *
{@link org.elasticsearch.util.gcommon.collect.Maps} + *
{@link org.elasticsearch.util.gcommon.collect.Ordering} + *
{@link org.elasticsearch.util.gcommon.collect.Sets} + *
{@link org.elasticsearch.util.gcommon.collect.Multisets} + *
{@link org.elasticsearch.util.gcommon.collect.Multimaps} + *
{@link org.elasticsearch.util.gcommon.collect.ObjectArrays} + *
+ + *

Forwarding collections

+ * + *
+ *
{@link org.elasticsearch.util.gcommon.collect.ForwardingCollection } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingConcurrentMap } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingIterator } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingList } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingListIterator } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingMap } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingMapEntry } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingMultimap } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingMultiset } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingObject } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingQueue } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingSet } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingSortedMap } + *
{@link org.elasticsearch.util.gcommon.collect.ForwardingSortedSet } + *
+ */ +package org.elasticsearch.util.gcommon.collect; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/Injectors.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/Injectors.java index a7e38c4dbb5..e84b6e0b3e1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/Injectors.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/Injectors.java @@ -19,10 +19,10 @@ package org.elasticsearch.util.guice; -import com.google.inject.*; -import com.google.inject.internal.Sets; -import com.google.inject.matcher.Matcher; -import com.google.inject.name.Names; +import org.elasticsearch.util.guice.inject.*; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.guice.inject.name.Names; +import org.elasticsearch.util.gcommon.collect.Sets; import java.lang.reflect.Type; import java.util.Map; @@ -35,7 +35,7 @@ import java.util.Set; public class Injectors { /** - * Returns an instance of the given type with the {@link com.google.inject.name.Named} + * Returns an instance of the given type with the {@link org.elasticsearch.util.guice.inject.name.Named} * annotation value. *

* This method allows you to switch this code diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/ModulesFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/ModulesFactory.java index a18f476a07e..f3c72275b67 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/ModulesFactory.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/ModulesFactory.java @@ -19,7 +19,7 @@ package org.elasticsearch.util.guice; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.util.settings.Settings; diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/AbstractModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/AbstractModule.java new file mode 100644 index 00000000000..77bbc909a77 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/AbstractModule.java @@ -0,0 +1,239 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.binder.AnnotatedBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.AnnotatedConstantBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.spi.TypeConverter; +import org.elasticsearch.util.guice.inject.spi.TypeListener; +import java.lang.annotation.Annotation; + +/** + * A support class for {@link Module}s which reduces repetition and results in + * a more readable configuration. Simply extend this class, implement {@link + * #configure()}, and call the inherited methods which mirror those found in + * {@link Binder}. For example: + * + *

+ * public class MyModule extends AbstractModule {
+ *   protected void configure() {
+ *     bind(Service.class).to(ServiceImpl.class).in(Singleton.class);
+ *     bind(CreditCardPaymentService.class);
+ *     bind(PaymentService.class).to(CreditCardPaymentService.class);
+ *     bindConstant().annotatedWith(Names.named("port")).to(8080);
+ *   }
+ * }
+ * 
+ * + * @author crazybob@google.com (Bob Lee) + */ +public abstract class AbstractModule implements Module { + + Binder binder; + + public final synchronized void configure(Binder builder) { + checkState(this.binder == null, "Re-entry is not allowed."); + + this.binder = checkNotNull(builder, "builder"); + try { + configure(); + } + finally { + this.binder = null; + } + } + + /** + * Configures a {@link Binder} via the exposed methods. + */ + protected abstract void configure(); + + /** + * Gets direct access to the underlying {@code Binder}. + */ + protected Binder binder() { + return binder; + } + + /** + * @see Binder#bindScope(Class, Scope) + */ + protected void bindScope(Class scopeAnnotation, + Scope scope) { + binder.bindScope(scopeAnnotation, scope); + } + + /** + * @see Binder#bind(Key) + */ + protected LinkedBindingBuilder bind(Key key) { + return binder.bind(key); + } + + /** + * @see Binder#bind(TypeLiteral) + */ + protected AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) { + return binder.bind(typeLiteral); + } + + /** + * @see Binder#bind(Class) + */ + protected AnnotatedBindingBuilder bind(Class clazz) { + return binder.bind(clazz); + } + + /** + * @see Binder#bindConstant() + */ + protected AnnotatedConstantBindingBuilder bindConstant() { + return binder.bindConstant(); + } + + /** + * @see Binder#install(Module) + */ + protected void install(Module module) { + binder.install(module); + } + + /** + * @see Binder#addError(String, Object[]) + */ + protected void addError(String message, Object... arguments) { + binder.addError(message, arguments); + } + + /** + * @see Binder#addError(Throwable) + */ + protected void addError(Throwable t) { + binder.addError(t); + } + + /** + * @see Binder#addError(Message) + * @since 2.0 + */ + protected void addError(Message message) { + binder.addError(message); + } + + /** + * @see Binder#requestInjection(Object) + * @since 2.0 + */ + protected void requestInjection(Object instance) { + binder.requestInjection(instance); + } + + /** + * @see Binder#requestStaticInjection(Class[]) + */ + protected void requestStaticInjection(Class... types) { + binder.requestStaticInjection(types); + } + + /** + * Adds a dependency from this module to {@code key}. When the injector is + * created, Guice will report an error if {@code key} cannot be injected. + * Note that this requirement may be satisfied by implicit binding, such as + * a public no-arguments constructor. + * + * @since 2.0 + */ + protected void requireBinding(Key key) { + binder.getProvider(key); + } + + /** + * Adds a dependency from this module to {@code type}. When the injector is + * created, Guice will report an error if {@code type} cannot be injected. + * Note that this requirement may be satisfied by implicit binding, such as + * a public no-arguments constructor. + * + * @since 2.0 + */ + protected void requireBinding(Class type) { + binder.getProvider(type); + } + + /** + * @see Binder#getProvider(Key) + * @since 2.0 + */ + protected Provider getProvider(Key key) { + return binder.getProvider(key); + } + + /** + * @see Binder#getProvider(Class) + * @since 2.0 + */ + protected Provider getProvider(Class type) { + return binder.getProvider(type); + } + + /** + * @see Binder#convertToTypes + * @since 2.0 + */ + protected void convertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + binder.convertToTypes(typeMatcher, converter); + } + + /** + * @see Binder#currentStage() + * @since 2.0 + */ + protected Stage currentStage() { + return binder.currentStage(); + } + + /** + * @see Binder#getMembersInjector(Class) + * @since 2.0 + */ + protected MembersInjector getMembersInjector(Class type) { + return binder.getMembersInjector(type); + } + + /** + * @see Binder#getMembersInjector(TypeLiteral) + * @since 2.0 + */ + protected MembersInjector getMembersInjector(TypeLiteral type) { + return binder.getMembersInjector(type); + } + + /** + * @see Binder#bindListener(org.elasticsearch.util.guice.inject.matcher.Matcher, + * org.elasticsearch.util.guice.inject.spi.TypeListener) + * @since 2.0 + */ + protected void bindListener(Matcher> typeMatcher, + TypeListener listener) { + binder.bindListener(typeMatcher, listener); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/AbstractProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/AbstractProcessor.java new file mode 100644 index 00000000000..32d02fa30f9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/AbstractProcessor.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.ElementVisitor; +import org.elasticsearch.util.guice.inject.spi.InjectionRequest; +import org.elasticsearch.util.guice.inject.spi.MembersInjectorLookup; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.guice.inject.spi.ProviderLookup; +import org.elasticsearch.util.guice.inject.spi.ScopeBinding; +import org.elasticsearch.util.guice.inject.spi.StaticInjectionRequest; +import org.elasticsearch.util.guice.inject.spi.TypeConverterBinding; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; +import java.util.Iterator; +import java.util.List; + +/** + * Abstract base class for creating an injector from module elements. + * + *

Extending classes must return {@code true} from any overridden + * {@code visit*()} methods, in order for the element processor to remove the + * handled element. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +abstract class AbstractProcessor implements ElementVisitor { + + protected Errors errors; + protected InjectorImpl injector; + + protected AbstractProcessor(Errors errors) { + this.errors = errors; + } + + public void process(Iterable isolatedInjectorBuilders) { + for (InjectorShell injectorShell : isolatedInjectorBuilders) { + process(injectorShell.getInjector(), injectorShell.getElements()); + } + } + + public void process(InjectorImpl injector, List elements) { + Errors errorsAnyElement = this.errors; + this.injector = injector; + try { + for (Iterator i = elements.iterator(); i.hasNext(); ) { + Element element = i.next(); + this.errors = errorsAnyElement.withSource(element.getSource()); + Boolean allDone = element.acceptVisitor(this); + if (allDone) { + i.remove(); + } + } + } finally { + this.errors = errorsAnyElement; + this.injector = null; + } + } + + public Boolean visit(Message message) { + return false; + } + + public Boolean visit(ScopeBinding scopeBinding) { + return false; + } + + public Boolean visit(InjectionRequest injectionRequest) { + return false; + } + + public Boolean visit(StaticInjectionRequest staticInjectionRequest) { + return false; + } + + public Boolean visit(TypeConverterBinding typeConverterBinding) { + return false; + } + + public Boolean visit(Binding binding) { + return false; + } + + public Boolean visit(ProviderLookup providerLookup) { + return false; + } + + public Boolean visit(PrivateElements privateElements) { + return false; + } + + public Boolean visit(MembersInjectorLookup lookup) { + return false; + } + + public Boolean visit(TypeListenerBinding binding) { + return false; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Binder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Binder.java new file mode 100644 index 00000000000..544c6c77d2e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Binder.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.binder.AnnotatedBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.AnnotatedConstantBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.spi.TypeConverter; +import org.elasticsearch.util.guice.inject.spi.TypeListener; +import java.lang.annotation.Annotation; + +/** + * Collects configuration information (primarily bindings) which will be + * used to create an {@link Injector}. Guice provides this object to your + * application's {@link Module} implementors so they may each contribute + * their own bindings and other registrations. + * + *

The Guice Binding EDSL

+ * + * Guice uses an embedded domain-specific language, or EDSL, to help you + * create bindings simply and readably. This approach is great for overall + * usability, but it does come with a small cost: it is difficult to + * learn how to use the Binding EDSL by reading + * method-level javadocs. Instead, you should consult the series of + * examples below. To save space, these examples omit the opening + * {@code binder}, just as you will if your module extends + * {@link AbstractModule}. + * + *
+ *     bind(ServiceImpl.class);
+ * + * This statement does essentially nothing; it "binds the {@code ServiceImpl} + * class to itself" and does not change Guice's default behavior. You may still + * want to use this if you prefer your {@link Module} class to serve as an + * explicit manifest for the services it provides. Also, in rare cases, + * Guice may be unable to validate a binding at injector creation time unless it + * is given explicitly. + * + *
+ *     bind(Service.class).to(ServiceImpl.class);
+ * + * Specifies that a request for a {@code Service} instance with no binding + * annotations should be treated as if it were a request for a + * {@code ServiceImpl} instance. This overrides the function of any + * {@link ImplementedBy @ImplementedBy} or {@link ProvidedBy @ProvidedBy} + * annotations found on {@code Service}, since Guice will have already + * "moved on" to {@code ServiceImpl} before it reaches the point when it starts + * looking for these annotations. + * + *
+ *     bind(Service.class).toProvider(ServiceProvider.class);
+ * + * In this example, {@code ServiceProvider} must extend or implement + * {@code Provider}. This binding specifies that Guice should resolve + * an unannotated injection request for {@code Service} by first resolving an + * instance of {@code ServiceProvider} in the regular way, then calling + * {@link Provider#get get()} on the resulting Provider instance to obtain the + * {@code Service} instance. + * + *

The {@link Provider} you use here does not have to be a "factory"; that + * is, a provider which always creates each instance it provides. + * However, this is generally a good practice to follow. You can then use + * Guice's concept of {@link Scope scopes} to guide when creation should happen + * -- "letting Guice work for you". + * + *

+ *     bind(Service.class).annotatedWith(Red.class).to(ServiceImpl.class);
+ * + * Like the previous example, but only applies to injection requests that use + * the binding annotation {@code @Red}. If your module also includes bindings + * for particular values of the {@code @Red} annotation (see below), + * then this binding will serve as a "catch-all" for any values of {@code @Red} + * that have no exact match in the bindings. + * + *
+ *     bind(ServiceImpl.class).in(Singleton.class);
+ *     // or, alternatively
+ *     bind(ServiceImpl.class).in(Scopes.SINGLETON);
+ * + * Either of these statements places the {@code ServiceImpl} class into + * singleton scope. Guice will create only one instance of {@code ServiceImpl} + * and will reuse it for all injection requests of this type. Note that it is + * still possible to bind another instance of {@code ServiceImpl} if the second + * binding is qualified by an annotation as in the previous example. Guice is + * not overly concerned with preventing you from creating multiple + * instances of your "singletons", only with enabling your application to + * share only one instance if that's all you tell Guice you need. + * + *

Note: a scope specified in this way overrides any scope that + * was specified with an annotation on the {@code ServiceImpl} class. + * + *

Besides {@link Singleton}/{@link Scopes#SINGLETON}, there are + * servlet-specific scopes available in + * {@code com.google.inject.servlet.ServletScopes}, and your Modules can + * contribute their own custom scopes for use here as well. + * + *

+ *     bind(new TypeLiteral<PaymentService<CreditCard>>() {})
+ *         .to(CreditCardPaymentService.class);
+ * + * This admittedly odd construct is the way to bind a parameterized type. It + * tells Guice how to honor an injection request for an element of type + * {@code PaymentService}. The class + * {@code CreditCardPaymentService} must implement the + * {@code PaymentService} interface. Guice cannot currently bind or + * inject a generic type, such as {@code Set}; all type parameters must be + * fully specified. + * + *
+ *     bind(Service.class).toInstance(new ServiceImpl());
+ *     // or, alternatively
+ *     bind(Service.class).toInstance(SomeLegacyRegistry.getService());
+ * + * In this example, your module itself, not Guice, takes responsibility + * for obtaining a {@code ServiceImpl} instance, then asks Guice to always use + * this single instance to fulfill all {@code Service} injection requests. When + * the {@link Injector} is created, it will automatically perform field + * and method injection for this instance, but any injectable constructor on + * {@code ServiceImpl} is simply ignored. Note that using this approach results + * in "eager loading" behavior that you can't control. + * + *
+ *     bindConstant().annotatedWith(ServerHost.class).to(args[0]);
+ * + * Sets up a constant binding. Constant injections must always be annotated. + * When a constant binding's value is a string, it is eligile for conversion to + * all primitive types, to {@link Enum#valueOf(Class, String) all enums}, and to + * {@link Class#forName class literals}. Conversions for other types can be + * configured using {@link #convertToTypes(Matcher, TypeConverter) + * convertToTypes()}. + * + *
+ *   {@literal @}Color("red") Color red; // A member variable (field)
+ *    . . .
+ *     red = MyModule.class.getDeclaredField("red").getAnnotation(Color.class);
+ *     bind(Service.class).annotatedWith(red).to(RedService.class);
+ * + * If your binding annotation has parameters you can apply different bindings to + * different specific values of your annotation. Getting your hands on the + * right instance of the annotation is a bit of a pain -- one approach, shown + * above, is to apply a prototype annotation to a field in your module class, so + * that you can read this annotation instance and give it to Guice. + * + *
+ *     bind(Service.class)
+ *         .annotatedWith(Names.named("blue"))
+ *         .to(BlueService.class);
+ * + * Differentiating by names is a common enough use case that we provided a + * standard annotation, {@link org.elasticsearch.util.guice.inject.name.Named @Named}. Because of + * Guice's library support, binding by name is quite easier than in the + * arbitrary binding annotation case we just saw. However, remember that these + * names will live in a single flat namespace with all the other names used in + * your application. + * + *

The above list of examples is far from exhaustive. If you can think of + * how the concepts of one example might coexist with the concepts from another, + * you can most likely weave the two together. If the two concepts make no + * sense with each other, you most likely won't be able to do it. In a few + * cases Guice will let something bogus slip by, and will then inform you of + * the problems at runtime, as soon as you try to create your Injector. + * + *

The other methods of Binder such as {@link #bindScope}, + * {@link #bindInterceptor}, {@link #install}, {@link #requestStaticInjection}, + * {@link #addError} and {@link #currentStage} are not part of the Binding EDSL; + * you can learn how to use these in the usual way, from the method + * documentation. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + * @author kevinb@google.com (Kevin Bourrillion) + */ +public interface Binder { + + /** + * Binds a scope to an annotation. + */ + void bindScope(Class annotationType, Scope scope); + + /** + * See the EDSL examples at {@link Binder}. + */ + LinkedBindingBuilder bind(Key key); + + /** + * See the EDSL examples at {@link Binder}. + */ + AnnotatedBindingBuilder bind(TypeLiteral typeLiteral); + + /** + * See the EDSL examples at {@link Binder}. + */ + AnnotatedBindingBuilder bind(Class type); + + /** + * See the EDSL examples at {@link Binder}. + */ + AnnotatedConstantBindingBuilder bindConstant(); + + /** + * Upon successful creation, the {@link Injector} will inject instance fields + * and methods of the given object. + * + * @param type of instance + * @param instance for which members will be injected + * @since 2.0 + */ + void requestInjection(TypeLiteral type, T instance); + + /** + * Upon successful creation, the {@link Injector} will inject instance fields + * and methods of the given object. + * + * @param instance for which members will be injected + * @since 2.0 + */ + void requestInjection(Object instance); + + /** + * Upon successful creation, the {@link Injector} will inject static fields + * and methods in the given classes. + * + * @param types for which static members will be injected + */ + void requestStaticInjection(Class... types); + + /** + * Uses the given module to configure more bindings. + */ + void install(Module module); + + /** + * Gets the current stage. + */ + Stage currentStage(); + + /** + * Records an error message which will be presented to the user at a later + * time. Unlike throwing an exception, this enable us to continue + * configuring the Injector and discover more errors. Uses {@link + * String#format(String, Object[])} to insert the arguments into the + * message. + */ + void addError(String message, Object... arguments); + + /** + * Records an exception, the full details of which will be logged, and the + * message of which will be presented to the user at a later + * time. If your Module calls something that you worry may fail, you should + * catch the exception and pass it into this. + */ + void addError(Throwable t); + + /** + * Records an error message to be presented to the user at a later time. + * + * @since 2.0 + */ + void addError(Message message); + + /** + * Returns the provider used to obtain instances for the given injection key. + * The returned will not be valid until the {@link Injector} has been + * created. The provider will throw an {@code IllegalStateException} if you + * try to use it beforehand. + * + * @since 2.0 + */ + Provider getProvider(Key key); + + /** + * Returns the provider used to obtain instances for the given injection type. + * The returned provider will not be valid until the {@link Injector} has been + * created. The provider will throw an {@code IllegalStateException} if you + * try to use it beforehand. + * + * @since 2.0 + */ + Provider getProvider(Class type); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * {@link Injector} has been created. The members injector will throw an {@code + * IllegalStateException} if you try to use it beforehand. + * + * @param typeLiteral type to get members injector for + * @since 2.0 + */ + MembersInjector getMembersInjector(TypeLiteral typeLiteral); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * {@link Injector} has been created. The members injector will throw an {@code + * IllegalStateException} if you try to use it beforehand. + * + * @param type type to get members injector for + * @since 2.0 + */ + MembersInjector getMembersInjector(Class type); + + /** + * Binds a type converter. The injector will use the given converter to + * convert string constants to matching types as needed. + * + * @param typeMatcher matches types the converter can handle + * @param converter converts values + * @since 2.0 + */ + void convertToTypes(Matcher> typeMatcher, + TypeConverter converter); + + /** + * Registers a listener for injectable types. Guice will notify the listener when it encounters + * injectable types matched by the given type matcher. + * + * @param typeMatcher that matches injectable types the listener should be notified of + * @param listener for injectable types matched by typeMatcher + * @since 2.0 + */ + void bindListener(Matcher> typeMatcher, + TypeListener listener); + + /** + * Returns a binder that uses {@code source} as the reference location for + * configuration errors. This is typically a {@link StackTraceElement} + * for {@code .java} source but it could any binding source, such as the + * path to a {@code .properties} file. + * + * @param source any object representing the source location and has a + * concise {@link Object#toString() toString()} value + * @return a binder that shares its configuration with this binder + * @since 2.0 + */ + Binder withSource(Object source); + + /** + * Returns a binder that skips {@code classesToSkip} when identify the + * calling code. The caller's {@link StackTraceElement} is used to locate + * the source of configuration errors. + * + * @param classesToSkip library classes that create bindings on behalf of + * their clients. + * @return a binder that shares its configuration with this binder. + * @since 2.0 + */ + Binder skipSources(Class... classesToSkip); + + /** + * Creates a new private child environment for bindings and other configuration. The returned + * binder can be used to add and configuration information in this environment. See {@link + * PrivateModule} for details. + * + * @return a binder that inherits configuration from this binder. Only exposed configuration on + * the returned binder will be visible to this binder. + * @since 2.0 + */ + PrivateBinder newPrivateBinder(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Binding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Binding.java new file mode 100644 index 00000000000..3019b7f13cf --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Binding.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.spi.BindingScopingVisitor; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.Element; + +/** + * A mapping from a key (type and optional annotation) to the strategy for getting instances of the + * type. This interface is part of the introspection API and is intended primarily for use by + * tools. + * + *

Bindings are created in several ways: + *

    + *
  • Explicitly in a module, via {@code bind()} and {@code bindConstant()} + * statements: + *
    + *     bind(Service.class).annotatedWith(Red.class).to(ServiceImpl.class);
    + *     bindConstant().annotatedWith(ServerHost.class).to(args[0]);
  • + *
  • Implicitly by the Injector by following a type's {@link ImplementedBy + * pointer} {@link ProvidedBy annotations} or by using its {@link Inject annotated} or + * default constructor.
  • + *
  • By converting a bound instance to a different type.
  • + *
  • For {@link Provider providers}, by delegating to the binding for the provided type.
  • + *
+ * + * + *

They exist on both modules and on injectors, and their behaviour is different for each: + *

    + *
  • Module bindings are incomplete and cannot be used to provide instances. + * This is because the applicable scopes and interceptors may not be known until an injector + * is created. From a tool's perspective, module bindings are like the injector's source + * code. They can be inspected or rewritten, but this analysis must be done statically.
  • + *
  • Injector bindings are complete and valid and can be used to provide + * instances. From a tools' perspective, injector bindings are like reflection for an + * injector. They have full runtime information, including the complete graph of injections + * necessary to satisfy a binding.
  • + *
+ * + * @param the bound type. The injected is always assignable to this type. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +public interface Binding extends Element { + + /** + * Returns the key for this binding. + */ + Key getKey(); + + /** + * Returns the scoped provider guice uses to fulfill requests for this + * binding. + * + * @throws UnsupportedOperationException when invoked on a {@link Binding} + * created via {@link org.elasticsearch.util.guice.inject.spi.Elements#getElements}. This + * method is only supported on {@link Binding}s returned from an injector. + */ + Provider getProvider(); + + /** + * Accepts a target visitor. Invokes the visitor method specific to this binding's target. + * + * @param visitor to call back on + * @since 2.0 + */ + V acceptTargetVisitor(BindingTargetVisitor visitor); + + /** + * Accepts a scoping visitor. Invokes the visitor method specific to this binding's scoping. + * + * @param visitor to call back on + * @since 2.0 + */ + V acceptScopingVisitor(BindingScopingVisitor visitor); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BindingAnnotation.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BindingAnnotation.java new file mode 100644 index 00000000000..2e74033db77 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BindingAnnotation.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates annotations which are used for binding. Only one such annotation + * may apply to a single injection point. You must also annotate binder + * annotations with {@code @Retention(RUNTIME)}. For example: + * + *
+ *   {@code @}Retention(RUNTIME)
+ *   {@code @}Target({ FIELD, PARAMETER, METHOD })
+ *   {@code @}BindingAnnotation
+ *   public {@code @}interface Transactional {}
+ * 
+ * + * @author crazybob@google.com (Bob Lee) + */ +@Target(ANNOTATION_TYPE) +@Retention(RUNTIME) +public @interface BindingAnnotation {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BindingProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BindingProcessor.java new file mode 100644 index 00000000000..bbbc3b90709 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BindingProcessor.java @@ -0,0 +1,289 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Annotations; +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.ExposedBindingImpl; +import org.elasticsearch.util.guice.inject.internal.InstanceBindingImpl; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.internal.LinkedBindingImpl; +import org.elasticsearch.util.guice.inject.internal.LinkedProviderBindingImpl; +import org.elasticsearch.util.guice.inject.internal.ProviderInstanceBindingImpl; +import org.elasticsearch.util.guice.inject.internal.ProviderMethod; +import org.elasticsearch.util.guice.inject.internal.Scoping; +import org.elasticsearch.util.guice.inject.internal.UntargettedBindingImpl; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.ConstructorBinding; +import org.elasticsearch.util.guice.inject.spi.ConvertedConstantBinding; +import org.elasticsearch.util.guice.inject.spi.ExposedBinding; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.InstanceBinding; +import org.elasticsearch.util.guice.inject.spi.LinkedKeyBinding; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.guice.inject.spi.ProviderBinding; +import org.elasticsearch.util.guice.inject.spi.ProviderInstanceBinding; +import org.elasticsearch.util.guice.inject.spi.ProviderKeyBinding; +import org.elasticsearch.util.guice.inject.spi.UntargettedBinding; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; +import java.util.Set; + +/** + * Handles {@link Binder#bind} and {@link Binder#bindConstant} elements. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +class BindingProcessor extends AbstractProcessor { + + private final List creationListeners = Lists.newArrayList(); + private final Initializer initializer; + private final List uninitializedBindings = Lists.newArrayList(); + + BindingProcessor(Errors errors, Initializer initializer) { + super(errors); + this.initializer = initializer; + } + + @Override public Boolean visit(Binding command) { + final Object source = command.getSource(); + + if (Void.class.equals(command.getKey().getRawType())) { + if (command instanceof ProviderInstanceBinding + && ((ProviderInstanceBinding) command).getProviderInstance() instanceof ProviderMethod) { + errors.voidProviderMethod(); + } else { + errors.missingConstantValues(); + } + return true; + } + + final Key key = command.getKey(); + Class rawType = key.getTypeLiteral().getRawType(); + + if (rawType == Provider.class) { + errors.bindingToProvider(); + return true; + } + + validateKey(command.getSource(), command.getKey()); + + final Scoping scoping = Scopes.makeInjectable( + ((BindingImpl) command).getScoping(), injector, errors); + + command.acceptTargetVisitor(new BindingTargetVisitor() { + + public Void visit(InstanceBinding binding) { + Set injectionPoints = binding.getInjectionPoints(); + T instance = binding.getInstance(); + Initializable ref = initializer.requestInjection( + injector, instance, source, injectionPoints); + ConstantFactory factory = new ConstantFactory(ref); + InternalFactory scopedFactory = Scopes.scope(key, injector, factory, scoping); + putBinding(new InstanceBindingImpl(injector, key, source, scopedFactory, injectionPoints, + instance)); + return null; + } + + public Void visit(ProviderInstanceBinding binding) { + Provider provider = binding.getProviderInstance(); + Set injectionPoints = binding.getInjectionPoints(); + Initializable> initializable = initializer + .>requestInjection(injector, provider, source, injectionPoints); + InternalFactory factory = new InternalFactoryToProviderAdapter(initializable, source); + InternalFactory scopedFactory = Scopes.scope(key, injector, factory, scoping); + putBinding(new ProviderInstanceBindingImpl(injector, key, source, scopedFactory, scoping, + provider, injectionPoints)); + return null; + } + + public Void visit(ProviderKeyBinding binding) { + Key> providerKey = binding.getProviderKey(); + BoundProviderFactory boundProviderFactory + = new BoundProviderFactory(injector, providerKey, source); + creationListeners.add(boundProviderFactory); + InternalFactory scopedFactory = Scopes.scope( + key, injector, (InternalFactory) boundProviderFactory, scoping); + putBinding(new LinkedProviderBindingImpl( + injector, key, source, scopedFactory, scoping, providerKey)); + return null; + } + + public Void visit(LinkedKeyBinding binding) { + Key linkedKey = binding.getLinkedKey(); + if (key.equals(linkedKey)) { + errors.recursiveBinding(); + } + + FactoryProxy factory = new FactoryProxy(injector, key, linkedKey, source); + creationListeners.add(factory); + InternalFactory scopedFactory = Scopes.scope(key, injector, factory, scoping); + putBinding( + new LinkedBindingImpl(injector, key, source, scopedFactory, scoping, linkedKey)); + return null; + } + + public Void visit(UntargettedBinding untargetted) { + // Error: Missing implementation. + // Example: bind(Date.class).annotatedWith(Red.class); + // We can't assume abstract types aren't injectable. They may have an + // @ImplementedBy annotation or something. + if (key.hasAnnotationType()) { + errors.missingImplementation(key); + putBinding(invalidBinding(injector, key, source)); + return null; + } + + // This cast is safe after the preceeding check. + final BindingImpl binding; + try { + binding = injector.createUnitializedBinding(key, scoping, source, errors); + putBinding(binding); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + putBinding(invalidBinding(injector, key, source)); + return null; + } + + uninitializedBindings.add(new Runnable() { + public void run() { + try { + ((InjectorImpl) binding.getInjector()).initializeBinding( + binding, errors.withSource(source)); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + }); + + return null; + } + + public Void visit(ExposedBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + public Void visit(ConvertedConstantBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + public Void visit(ConstructorBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + + public Void visit(ProviderBinding binding) { + throw new IllegalArgumentException("Cannot apply a non-module element"); + } + }); + + return true; + } + + @Override public Boolean visit(PrivateElements privateElements) { + for (Key key : privateElements.getExposedKeys()) { + bindExposed(privateElements, key); + } + return false; // leave the private elements for the PrivateElementsProcessor to handle + } + + private void bindExposed(PrivateElements privateElements, Key key) { + ExposedKeyFactory exposedKeyFactory = new ExposedKeyFactory(key, privateElements); + creationListeners.add(exposedKeyFactory); + putBinding(new ExposedBindingImpl( + injector, privateElements.getExposedSource(key), key, exposedKeyFactory, privateElements)); + } + + private void validateKey(Object source, Key key) { + Annotations.checkForMisplacedScopeAnnotations(key.getRawType(), source, errors); + } + + UntargettedBindingImpl invalidBinding(InjectorImpl injector, Key key, Object source) { + return new UntargettedBindingImpl(injector, key, source); + } + + public void initializeBindings() { + for (Runnable initializer : uninitializedBindings) { + initializer.run(); + } + } + + public void runCreationListeners() { + for (CreationListener creationListener : creationListeners) { + creationListener.notify(errors); + } + } + + private void putBinding(BindingImpl binding) { + Key key = binding.getKey(); + + Class rawType = key.getRawType(); + if (FORBIDDEN_TYPES.contains(rawType)) { + errors.cannotBindToGuiceType(rawType.getSimpleName()); + return; + } + + Binding original = injector.state.getExplicitBinding(key); + if (original != null && !isOkayDuplicate(original, binding)) { + errors.bindingAlreadySet(key, original.getSource()); + return; + } + + // prevent the parent from creating a JIT binding for this key + injector.state.parent().blacklist(key); + injector.state.putBinding(key, binding); + } + + /** + * We tolerate duplicate bindings only if one exposes the other. + * + * @param original the binding in the parent injector (candidate for an exposing binding) + * @param binding the binding to check (candidate for the exposed binding) + */ + private boolean isOkayDuplicate(Binding original, BindingImpl binding) { + if (original instanceof ExposedBindingImpl) { + ExposedBindingImpl exposed = (ExposedBindingImpl) original; + InjectorImpl exposedFrom = (InjectorImpl) exposed.getPrivateElements().getInjector(); + return (exposedFrom == binding.getInjector()); + } + return false; + } + + // It's unfortunate that we have to maintain a blacklist of specific + // classes, but we can't easily block the whole package because of + // all our unit tests. + private static final Set> FORBIDDEN_TYPES = ImmutableSet.of( + AbstractModule.class, + Binder.class, + Binding.class, + Injector.class, + Key.class, + MembersInjector.class, + Module.class, + Provider.class, + Scope.class, + TypeLiteral.class); + // TODO(jessewilson): fix BuiltInModule, then add Stage + + interface CreationListener { + void notify(Errors errors); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BoundProviderFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BoundProviderFactory.java new file mode 100644 index 00000000000..828a8ef880c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/BoundProviderFactory.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.BindingProcessor.CreationListener; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * Delegates to a custom factory which is also bound in the injector. + */ +class BoundProviderFactory implements InternalFactory, CreationListener { + + private final InjectorImpl injector; + final Key> providerKey; + final Object source; + private InternalFactory> providerFactory; + + BoundProviderFactory( + InjectorImpl injector, + Key> providerKey, + Object source) { + this.injector = injector; + this.providerKey = providerKey; + this.source = source; + } + + public void notify(Errors errors) { + try { + providerFactory = injector.getInternalFactory(providerKey, errors.withSource(source)); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + errors = errors.withSource(providerKey); + Provider provider = providerFactory.get(errors, context, dependency); + try { + return errors.checkForNull(provider.get(), source, dependency); + } catch(RuntimeException userException) { + throw errors.errorInProvider(userException).toException(); + } + } + + @Override public String toString() { + return providerKey.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConfigurationException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConfigurationException.java new file mode 100644 index 00000000000..7527ef843b5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConfigurationException.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Collection; + +/** + * Thrown when a programming error such as a misplaced annotation, illegal binding, or unsupported + * scope is found. Clients should catch this exception, log it, and stop execution. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class ConfigurationException extends RuntimeException { + + private final ImmutableSet messages; + private Object partialValue = null; + + /** Creates a ConfigurationException containing {@code messages}. */ + public ConfigurationException(Iterable messages) { + this.messages = ImmutableSet.copyOf(messages); + initCause(Errors.getOnlyCause(this.messages)); + } + + /** Returns a copy of this configuration exception with the specified partial value. */ + public ConfigurationException withPartialValue(Object partialValue) { + checkState(this.partialValue == null, + "Can't clobber existing partial value %s with %s", this.partialValue, partialValue); + ConfigurationException result = new ConfigurationException(messages); + result.partialValue = partialValue; + return result; + } + + /** Returns messages for the errors that caused this exception. */ + public Collection getErrorMessages() { + return messages; + } + + /** + * Returns a value that was only partially computed due to this exception. The caller can use + * this while collecting additional configuration problems. + * + * @return the partial value, or {@code null} if none was set. The type of the partial value is + * specified by the throwing method. + */ + @SuppressWarnings("unchecked") // this is *extremely* unsafe. We trust the caller here. + public E getPartialValue() { + return (E) partialValue; + } + + @Override public String getMessage() { + return Errors.format("Guice configuration errors", messages); + } + + private static final long serialVersionUID = 0; +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstantFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstantFactory.java new file mode 100644 index 00000000000..7cf9283ee06 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstantFactory.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.internal.ToStringBuilder; +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * @author crazybob@google.com (Bob Lee) + */ +class ConstantFactory implements InternalFactory { + + private final Initializable initializable; + + public ConstantFactory(Initializable initializable) { + this.initializable = initializable; + } + + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + return initializable.get(errors); + } + + public String toString() { + return new ToStringBuilder(ConstantFactory.class) + .add("value", initializable) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructionProxy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructionProxy.java new file mode 100644 index 00000000000..b8c12fe9621 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructionProxy.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Proxies calls to a {@link java.lang.reflect.Constructor} for a class + * {@code T}. + * + * @author crazybob@google.com (Bob Lee) + */ +interface ConstructionProxy { + + /** + * Constructs an instance of {@code T} for the given arguments. + */ + T newInstance(Object... arguments) throws InvocationTargetException; + + /** + * Returns the injection point for this constructor. + */ + InjectionPoint getInjectionPoint(); + + /** + * Returns the injected constructor. If the injected constructor is synthetic (such as generated + * code for method interception), the natural constructor is returned. + */ + Constructor getConstructor(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructionProxyFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructionProxyFactory.java new file mode 100644 index 00000000000..987f55a21ac --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructionProxyFactory.java @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * Creates {@link ConstructionProxy} instances. + * + * @author crazybob@google.com (Bob Lee) + */ +interface ConstructionProxyFactory { + + /** + * Gets a construction proxy for the given constructor. + */ + ConstructionProxy create(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorBindingImpl.java new file mode 100644 index 00000000000..93be15b10fb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorBindingImpl.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.internal.Scoping; +import org.elasticsearch.util.guice.inject.internal.ToStringBuilder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.ConstructorBinding; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Set; + +class ConstructorBindingImpl extends BindingImpl implements ConstructorBinding { + + private final Factory factory; + + private ConstructorBindingImpl(Injector injector, Key key, Object source, + InternalFactory scopedFactory, Scoping scoping, Factory factory) { + super(injector, key, source, scopedFactory, scoping); + this.factory = factory; + } + + static ConstructorBindingImpl create( + InjectorImpl injector, Key key, Object source, Scoping scoping) { + Factory factoryFactory = new Factory(); + InternalFactory scopedFactory + = Scopes.scope(key, injector, factoryFactory, scoping); + return new ConstructorBindingImpl( + injector, key, source, scopedFactory, scoping, factoryFactory); + } + + public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { + factory.constructorInjector = injector.constructors.get(getKey().getTypeLiteral(), errors); + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + checkState(factory.constructorInjector != null, "not initialized"); + return visitor.visit(this); + } + + public InjectionPoint getConstructor() { + checkState(factory.constructorInjector != null, "Binding is not ready"); + return factory.constructorInjector.getConstructionProxy().getInjectionPoint(); + } + + public Set getInjectableMembers() { + checkState(factory.constructorInjector != null, "Binding is not ready"); + return factory.constructorInjector.getInjectableMembers(); + } + + public Set> getDependencies() { + return Dependency.forInjectionPoints(new ImmutableSet.Builder() + .add(getConstructor()) + .addAll(getInjectableMembers()) + .build()); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } + + @Override public String toString() { + return new ToStringBuilder(ConstructorBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .toString(); + } + + private static class Factory implements InternalFactory { + private ConstructorInjector constructorInjector; + + @SuppressWarnings("unchecked") + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + checkState(constructorInjector != null, "Constructor not ready"); + + // This may not actually be safe because it could return a super type of T (if that's all the + // client needs), but it should be OK in practice thanks to the wonders of erasure. + return (T) constructorInjector.construct(errors, context, dependency.getKey().getRawType()); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorInjector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorInjector.java new file mode 100644 index 00000000000..10ad380ba05 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorInjector.java @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.ConstructionContext; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.lang.reflect.InvocationTargetException; + +/** + * Creates instances using an injectable constructor. After construction, all injectable fields and + * methods are injected. + * + * @author crazybob@google.com (Bob Lee) + */ +class ConstructorInjector { + + private final ImmutableSet injectableMembers; + private final SingleParameterInjector[] parameterInjectors; + private final ConstructionProxy constructionProxy; + private final MembersInjectorImpl membersInjector; + + ConstructorInjector(ImmutableSet injectableMembers, + ConstructionProxy constructionProxy, + SingleParameterInjector[] parameterInjectors, + MembersInjectorImpl membersInjector) + throws ErrorsException { + this.injectableMembers = injectableMembers; + this.constructionProxy = constructionProxy; + this.parameterInjectors = parameterInjectors; + this.membersInjector = membersInjector; + } + + public ImmutableSet getInjectableMembers() { + return injectableMembers; + } + + ConstructionProxy getConstructionProxy() { + return constructionProxy; + } + + /** + * Construct an instance. Returns {@code Object} instead of {@code T} because + * it may return a proxy. + */ + Object construct(Errors errors, InternalContext context, Class expectedType) + throws ErrorsException { + ConstructionContext constructionContext = context.getConstructionContext(this); + + // We have a circular reference between constructors. Return a proxy. + if (constructionContext.isConstructing()) { + // TODO (crazybob): if we can't proxy this object, can we proxy the other object? + return constructionContext.createProxy(errors, expectedType); + } + + // If we're re-entering this factory while injecting fields or methods, + // return the same instance. This prevents infinite loops. + T t = constructionContext.getCurrentReference(); + if (t != null) { + return t; + } + + try { + // First time through... + constructionContext.startConstruction(); + try { + Object[] parameters = SingleParameterInjector.getAll(errors, context, parameterInjectors); + t = constructionProxy.newInstance(parameters); + constructionContext.setProxyDelegates(t); + } finally { + constructionContext.finishConstruction(); + } + + // Store reference. If an injector re-enters this factory, they'll get the same reference. + constructionContext.setCurrentReference(t); + + membersInjector.injectMembers(t, errors, context); + membersInjector.notifyListeners(t, errors); + + return t; + } catch (InvocationTargetException userException) { + Throwable cause = userException.getCause() != null + ? userException.getCause() + : userException; + throw errors.withSource(constructionProxy.getInjectionPoint()) + .errorInjectingConstructor(cause).toException(); + } finally { + constructionContext.removeCurrentReference(); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorInjectorStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorInjectorStore.java new file mode 100644 index 00000000000..dbbfb21ab61 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ConstructorInjectorStore.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.FailableCache; +import static org.elasticsearch.util.gcommon.collect.Iterables.concat; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; + +/** + * Constructor injectors by type. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class ConstructorInjectorStore { + private final InjectorImpl injector; + + private final FailableCache, ConstructorInjector> cache + = new FailableCache, ConstructorInjector> () { + @SuppressWarnings("unchecked") + protected ConstructorInjector create(TypeLiteral type, Errors errors) + throws ErrorsException { + return createConstructor(type, errors); + } + }; + + ConstructorInjectorStore(InjectorImpl injector) { + this.injector = injector; + } + + /** + * Returns a new complete constructor injector with injection listeners registered. + */ + @SuppressWarnings("unchecked") // the ConstructorInjector type always agrees with the passed type + public ConstructorInjector get(TypeLiteral key, Errors errors) throws ErrorsException { + return (ConstructorInjector) cache.get(key, errors); + } + + private ConstructorInjector createConstructor(TypeLiteral type, Errors errors) + throws ErrorsException { + int numErrorsBefore = errors.size(); + + InjectionPoint injectionPoint; + try { + injectionPoint = InjectionPoint.forConstructorOf(type); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + throw errors.toException(); + } + + SingleParameterInjector[] constructorParameterInjectors + = injector.getParametersInjectors(injectionPoint.getDependencies(), errors); + MembersInjectorImpl membersInjector = injector.membersInjectorStore.get(type, errors); + + ConstructionProxyFactory factory = new DefaultConstructionProxyFactory(injectionPoint); + + errors.throwIfNewErrors(numErrorsBefore); + + return new ConstructorInjector(membersInjector.getInjectionPoints(), factory.create(), + constructorParameterInjectors, membersInjector); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ContextualCallable.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ContextualCallable.java new file mode 100644 index 00000000000..5955db05195 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ContextualCallable.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; + +/** + * @author crazybob@google.com (Bob Lee) +*/ +interface ContextualCallable { + T call(InternalContext context) throws ErrorsException; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/CreationException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/CreationException.java new file mode 100644 index 00000000000..860e0a37e06 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/CreationException.java @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Collection; + +/** + * Thrown when errors occur while creating a {@link Injector}. Includes a list of encountered + * errors. Clients should catch this exception, log it, and stop execution. + * + * @author crazybob@google.com (Bob Lee) + */ +public class CreationException extends RuntimeException { + + private final ImmutableSet messages; + + /** Creates a CreationException containing {@code messages}. */ + public CreationException(Collection messages) { + this.messages = ImmutableSet.copyOf(messages); + checkArgument(!this.messages.isEmpty()); + initCause(Errors.getOnlyCause(this.messages)); + } + + /** Returns messages for the errors that caused this exception. */ + public Collection getErrorMessages() { + return messages; + } + + @Override public String getMessage() { + return Errors.format("Guice creation errors", messages); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/DefaultConstructionProxyFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/DefaultConstructionProxyFactory.java new file mode 100644 index 00000000000..491e0404125 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/DefaultConstructionProxyFactory.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +/** + * Produces construction proxies that invoke the class constructor. + * + * @author crazybob@google.com (Bob Lee) + */ +class DefaultConstructionProxyFactory implements ConstructionProxyFactory { + + private final InjectionPoint injectionPoint; + + /** + * @param injectionPoint an injection point whose member is a constructor of {@code T}. + */ + DefaultConstructionProxyFactory(InjectionPoint injectionPoint) { + this.injectionPoint = injectionPoint; + } + + public ConstructionProxy create() { + @SuppressWarnings("unchecked") // the injection point is for a constructor of T + final Constructor constructor = (Constructor) injectionPoint.getMember(); + + // Use FastConstructor if the constructor is public. + if (Modifier.isPublic(constructor.getModifiers())) { + } else { + constructor.setAccessible(true); + } + + return new ConstructionProxy() { + public T newInstance(Object... arguments) throws InvocationTargetException { + try { + return constructor.newInstance(arguments); + } catch (InstantiationException e) { + throw new AssertionError(e); // shouldn't happen, we know this is a concrete type + } catch (IllegalAccessException e) { + throw new AssertionError(e); // a security manager is blocking us, we're hosed + } + } + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + public Constructor getConstructor() { + return constructor; + } + }; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/DeferredLookups.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/DeferredLookups.java new file mode 100644 index 00000000000..64c38571f46 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/DeferredLookups.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.MembersInjectorLookup; +import org.elasticsearch.util.guice.inject.spi.ProviderLookup; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; + +/** + * Returns providers and members injectors that haven't yet been initialized. As a part of injector + * creation it's necessary to {@link #initialize initialize} these lookups. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class DeferredLookups implements Lookups { + private final InjectorImpl injector; + private final List lookups = Lists.newArrayList(); + + public DeferredLookups(InjectorImpl injector) { + this.injector = injector; + } + + /** + * Initialize the specified lookups, either immediately or when the injector is created. + */ + public void initialize(Errors errors) { + injector.lookups = injector; + new LookupProcessor(errors).process(injector, lookups); + } + + public Provider getProvider(Key key) { + ProviderLookup lookup = new ProviderLookup(key, key); + lookups.add(lookup); + return lookup.getProvider(); + } + + public MembersInjector getMembersInjector(TypeLiteral type) { + MembersInjectorLookup lookup = new MembersInjectorLookup(type, type); + lookups.add(lookup); + return lookup.getMembersInjector(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/EncounterImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/EncounterImpl.java new file mode 100644 index 00000000000..63f89b5d53c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/EncounterImpl.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; + +import org.elasticsearch.util.guice.inject.spi.InjectionListener; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.spi.TypeEncounter; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +final class EncounterImpl implements TypeEncounter { + + private final Errors errors; + private final Lookups lookups; + private List> membersInjectors; // lazy + private List> injectionListeners; // lazy + private boolean valid = true; + + public EncounterImpl(Errors errors, Lookups lookups) { + this.errors = errors; + this.lookups = lookups; + } + + public void invalidate() { + valid = false; + } + + public ImmutableList> getMembersInjectors() { + return membersInjectors == null + ? ImmutableList.>of() + : ImmutableList.copyOf(membersInjectors); + } + + public ImmutableList> getInjectionListeners() { + return injectionListeners == null + ? ImmutableList.>of() + : ImmutableList.copyOf(injectionListeners); + } + + public void register(MembersInjector membersInjector) { + checkState(valid, "Encounters may not be used after hear() returns."); + + if (membersInjectors == null) { + membersInjectors = Lists.newArrayList(); + } + + membersInjectors.add(membersInjector); + } + + public void register(InjectionListener injectionListener) { + checkState(valid, "Encounters may not be used after hear() returns."); + + if (injectionListeners == null) { + injectionListeners = Lists.newArrayList(); + } + + injectionListeners.add(injectionListener); + } + + public void addError(String message, Object... arguments) { + checkState(valid, "Encounters may not be used after hear() returns."); + errors.addMessage(message, arguments); + } + + public void addError(Throwable t) { + checkState(valid, "Encounters may not be used after hear() returns."); + errors.errorInUserCode(t, "An exception was caught and reported. Message: %s", t.getMessage()); + } + + public void addError(Message message) { + checkState(valid, "Encounters may not be used after hear() returns."); + errors.addMessage(message); + } + + public Provider getProvider(Key key) { + checkState(valid, "Encounters may not be used after hear() returns."); + return lookups.getProvider(key); + } + + public Provider getProvider(Class type) { + return getProvider(Key.get(type)); + } + + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + checkState(valid, "Encounters may not be used after hear() returns."); + return lookups.getMembersInjector(typeLiteral); + } + + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Exposed.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Exposed.java new file mode 100644 index 00000000000..abe0414aaa6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Exposed.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Acccompanies a {@literal @}{@link org.elasticsearch.util.guice.inject.Provides Provides} method annotation in a + * private module to indicate that the provided binding is exposed. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +@Target(ElementType.METHOD) @Retention(RUNTIME) @Documented +public @interface Exposed {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ExposedKeyFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ExposedKeyFactory.java new file mode 100644 index 00000000000..8f3645f6446 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ExposedKeyFactory.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; + +/** + * This factory exists in a parent injector. When invoked, it retrieves its value from a child + * injector. + */ +class ExposedKeyFactory implements InternalFactory, BindingProcessor.CreationListener { + private final Key key; + private final PrivateElements privateElements; + private BindingImpl delegate; + + public ExposedKeyFactory(Key key, PrivateElements privateElements) { + this.key = key; + this.privateElements = privateElements; + } + + public void notify(Errors errors) { + InjectorImpl privateInjector = (InjectorImpl) privateElements.getInjector(); + BindingImpl explicitBinding = privateInjector.state.getExplicitBinding(key); + + // validate that the child injector has its own factory. If the getInternalFactory() returns + // this, then that child injector doesn't have a factory (and getExplicitBinding has returned + // its parent's binding instead + if (explicitBinding.getInternalFactory() == this) { + errors.withSource(explicitBinding.getSource()).exposedButNotBound(key); + return; + } + + this.delegate = explicitBinding; + } + + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + return delegate.getInternalFactory().get(errors, context, dependency); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/FactoryProxy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/FactoryProxy.java new file mode 100644 index 00000000000..a7e2afddee3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/FactoryProxy.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.internal.ToStringBuilder; +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * A placeholder which enables us to swap in the real factory once the injector is created. + */ +class FactoryProxy implements InternalFactory, BindingProcessor.CreationListener { + + private final InjectorImpl injector; + private final Key key; + private final Key targetKey; + private final Object source; + + private InternalFactory targetFactory; + + FactoryProxy(InjectorImpl injector, Key key, Key targetKey, Object source) { + this.injector = injector; + this.key = key; + this.targetKey = targetKey; + this.source = source; + } + + public void notify(final Errors errors) { + try { + targetFactory = injector.getInternalFactory(targetKey, errors.withSource(source)); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + return targetFactory.get(errors.withSource(targetKey), context, dependency); + } + + @Override public String toString() { + return new ToStringBuilder(FactoryProxy.class) + .add("key", key) + .add("provider", targetFactory) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Guice.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Guice.java new file mode 100644 index 00000000000..bede4956cec --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Guice.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import java.util.Arrays; + +/** + * The entry point to the Guice framework. Creates {@link Injector}s from + * {@link Module}s. + * + *

Guice supports a model of development that draws clear boundaries between + * APIs, Implementations of these APIs, Modules which configure these + * implementations, and finally Applications which consist of a collection of + * Modules. It is the Application, which typically defines your {@code main()} + * method, that bootstraps the Guice Injector using the {@code Guice} class, as + * in this example: + *

+ *     public class FooApplication {
+ *       public static void main(String[] args) {
+ *         Injector injector = Guice.createInjector(
+ *             new ModuleA(),
+ *             new ModuleB(),
+ *             . . .
+ *             new FooApplicationFlagsModule(args)
+ *         );
+ *
+ *         // Now just bootstrap the application and you're done
+ *         FooStarter starter = injector.getInstance(FooStarter.class);
+ *         starter.runApplication();
+ *       }
+ *     }
+ * 
+ */ +public final class Guice { + + private Guice() {} + + /** + * Creates an injector for the given set of modules. + * + * @throws CreationException if one or more errors occur during Injector + * construction + */ + public static Injector createInjector(Module... modules) { + return createInjector(Arrays.asList(modules)); + } + + /** + * Creates an injector for the given set of modules. + * + * @throws CreationException if one or more errors occur during Injector + * creation + */ + public static Injector createInjector(Iterable modules) { + return createInjector(Stage.DEVELOPMENT, modules); + } + + /** + * Creates an injector for the given set of modules, in a given development + * stage. + * + * @throws CreationException if one or more errors occur during Injector + * creation + */ + public static Injector createInjector(Stage stage, Module... modules) { + return createInjector(stage, Arrays.asList(modules)); + } + + /** + * Creates an injector for the given set of modules, in a given development + * stage. + * + * @throws CreationException if one or more errors occur during Injector + * construction + */ + public static Injector createInjector(Stage stage, + Iterable modules) { + return new InjectorBuilder() + .stage(stage) + .addModules(modules) + .build(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ImplementedBy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ImplementedBy.java new file mode 100644 index 00000000000..9b5277a0aca --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ImplementedBy.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * A pointer to the default implementation of a type. + * + * @author crazybob@google.com (Bob Lee) + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface ImplementedBy { + + /** + * The implementation type. + */ + Class value(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InheritingState.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InheritingState.java new file mode 100644 index 00000000000..302c4217268 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InheritingState.java @@ -0,0 +1,131 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.MatcherAndConverter; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Maps; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +class InheritingState implements State { + + private final State parent; + + // Must be a linked hashmap in order to preserve order of bindings in Modules. + private final Map, Binding> explicitBindingsMutable = Maps.newLinkedHashMap(); + private final Map, Binding> explicitBindings + = Collections.unmodifiableMap(explicitBindingsMutable); + private final Map, Scope> scopes = Maps.newHashMap(); + private final List converters = Lists.newArrayList(); + private final List listenerBindings = Lists.newArrayList(); + private final WeakKeySet blacklistedKeys = new WeakKeySet(); + private final Object lock; + + InheritingState(State parent) { + this.parent = checkNotNull(parent, "parent"); + this.lock = (parent == State.NONE) ? this : parent.lock(); + } + + public State parent() { + return parent; + } + + @SuppressWarnings("unchecked") // we only put in BindingImpls that match their key types + public BindingImpl getExplicitBinding(Key key) { + Binding binding = explicitBindings.get(key); + return binding != null ? (BindingImpl) binding : parent.getExplicitBinding(key); + } + + public Map, Binding> getExplicitBindingsThisLevel() { + return explicitBindings; + } + + public void putBinding(Key key, BindingImpl binding) { + explicitBindingsMutable.put(key, binding); + } + + public Scope getScope(Class annotationType) { + Scope scope = scopes.get(annotationType); + return scope != null ? scope : parent.getScope(annotationType); + } + + public void putAnnotation(Class annotationType, Scope scope) { + scopes.put(annotationType, scope); + } + + public Iterable getConvertersThisLevel() { + return converters; + } + + public void addConverter(MatcherAndConverter matcherAndConverter) { + converters.add(matcherAndConverter); + } + + public MatcherAndConverter getConverter( + String stringValue, TypeLiteral type, Errors errors, Object source) { + MatcherAndConverter matchingConverter = null; + for (State s = this; s != State.NONE; s = s.parent()) { + for (MatcherAndConverter converter : s.getConvertersThisLevel()) { + if (converter.getTypeMatcher().matches(type)) { + if (matchingConverter != null) { + errors.ambiguousTypeConversion(stringValue, source, type, matchingConverter, converter); + } + matchingConverter = converter; + } + } + } + return matchingConverter; + } + + public void addTypeListener(TypeListenerBinding listenerBinding) { + listenerBindings.add(listenerBinding); + } + + public List getTypeListenerBindings() { + List parentBindings = parent.getTypeListenerBindings(); + List result + = new ArrayList(parentBindings.size() + 1); + result.addAll(parentBindings); + result.addAll(listenerBindings); + return result; + } + + public void blacklist(Key key) { + parent.blacklist(key); + blacklistedKeys.add(key); + } + + public boolean isBlacklisted(Key key) { + return blacklistedKeys.contains(key); + } + + public Object lock() { + return lock; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializable.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializable.java new file mode 100644 index 00000000000..68a8e06adf2 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializable.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; + +/** + * Holds a reference that requires initialization to be performed before it can be used. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +interface Initializable { + + /** + * Ensures the reference is initialized, then returns it. + */ + T get(Errors errors) throws ErrorsException; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializables.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializables.java new file mode 100644 index 00000000000..1893a8bfae0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializables.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +class Initializables { + + /** + * Returns an initializable for an instance that requires no initialization. + */ + static Initializable of(final T instance) { + return new Initializable() { + public T get(Errors errors) throws ErrorsException { + return instance; + } + + @Override public String toString() { + return String.valueOf(instance); + } + }; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializer.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializer.java new file mode 100644 index 00000000000..8fc7460b58e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Initializer.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Maps; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +/** + * Manages and injects instances at injector-creation time. This is made more complicated by + * instances that request other instances while they're being injected. We overcome this by using + * {@link Initializable}, which attempts to perform injection before use. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class Initializer { + /** the only thread that we'll use to inject members. */ + private final Thread creatingThread = Thread.currentThread(); + + /** zero means everything is injected. */ + private final CountDownLatch ready = new CountDownLatch(1); + + /** Maps instances that need injection to a source that registered them */ + private final Map> pendingInjection = Maps.newIdentityHashMap(); + + /** + * Registers an instance for member injection when that step is performed. + * + * @param instance an instance that optionally has members to be injected (each annotated with + * @Inject). + * @param source the source location that this injection was requested + */ + public Initializable requestInjection(InjectorImpl injector, T instance, Object source, + Set injectionPoints) { + checkNotNull(source); + + // short circuit if the object has no injections + if (instance == null + || (injectionPoints.isEmpty() && !injector.membersInjectorStore.hasTypeListeners())) { + return Initializables.of(instance); + } + + InjectableReference initializable = new InjectableReference(injector, instance, source); + pendingInjection.put(instance, initializable); + return initializable; + } + + /** + * Prepares member injectors for all injected instances. This prompts Guice to do static analysis + * on the injected instances. + */ + void validateOustandingInjections(Errors errors) { + for (InjectableReference reference : pendingInjection.values()) { + try { + reference.validate(errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + } + + /** + * Performs creation-time injections on all objects that require it. Whenever fulfilling an + * injection depends on another object that requires injection, we inject it first. If the two + * instances are codependent (directly or transitively), ordering of injection is arbitrary. + */ + void injectAll(final Errors errors) { + // loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy + // is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work + for (InjectableReference reference : Lists.newArrayList(pendingInjection.values())) { + try { + reference.get(errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + if (!pendingInjection.isEmpty()) { + throw new AssertionError("Failed to satisfy " + pendingInjection); + } + + ready.countDown(); + } + + private class InjectableReference implements Initializable { + private final InjectorImpl injector; + private final T instance; + private final Object source; + private MembersInjectorImpl membersInjector; + + public InjectableReference(InjectorImpl injector, T instance, Object source) { + this.injector = injector; + this.instance = checkNotNull(instance, "instance"); + this.source = checkNotNull(source, "source"); + } + + public void validate(Errors errors) throws ErrorsException { + @SuppressWarnings("unchecked") // the type of 'T' is a TypeLiteral + TypeLiteral type = TypeLiteral.get((Class) instance.getClass()); + membersInjector = injector.membersInjectorStore.get(type, errors.withSource(source)); + } + + /** + * Reentrant. If {@code instance} was registered for injection at injector-creation time, this + * method will ensure that all its members have been injected before returning. + */ + public T get(Errors errors) throws ErrorsException { + if (ready.getCount() == 0) { + return instance; + } + + // just wait for everything to be injected by another thread + if (Thread.currentThread() != creatingThread) { + try { + ready.await(); + return instance; + } catch (InterruptedException e) { + // Give up, since we don't know if our injection is ready + throw new RuntimeException(e); + } + } + + // toInject needs injection, do it right away. we only do this once, even if it fails + if (pendingInjection.remove(instance) != null) { + membersInjector.injectAndNotify(instance, errors.withSource(source)); + } + + return instance; + } + + @Override public String toString() { + return instance.toString(); + } + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Inject.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Inject.java new file mode 100644 index 00000000000..2e173f68c38 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Inject.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates members of your implementation class (constructors, methods + * and fields) into which the {@link Injector} should inject values. + * The Injector fulfills injection requests for: + * + *
    + *
  • Every instance it constructs. The class being constructed must have + * exactly one of its constructors marked with {@code @Inject} or must have a + * constructor taking no parameters. The Injector then proceeds to perform + * method and field injections. + * + *
  • Pre-constructed instances passed to {@link Injector#injectMembers}, + * {@link org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder#toInstance(Object)} and + * {@link org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder#toProvider(Provider)}. + * In this case all constructors are, of course, ignored. + * + *
  • Static fields and methods of classes which any {@link Module} has + * specifically requested static injection for, using + * {@link Binder#requestStaticInjection}. + *
+ * + * In all cases, a member can be injected regardless of its Java access + * specifier (private, default, protected, public). + * + * @author crazybob@google.com (Bob Lee) + */ +@Target({ METHOD, CONSTRUCTOR, FIELD }) +@Retention(RUNTIME) +@Documented +public @interface Inject { + + /** + * If true, and the appropriate binding is not found, + * the Injector will skip injection of this method or field rather than + * produce an error. When applied to a field, any default value already + * assigned to the field will remain (guice will not actively null out the + * field). When applied to a method, the method will only be invoked if + * bindings for all parameters are found. When applied to a + * constructor, an error will result upon Injector creation. + */ + boolean optional() default false; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectionRequestProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectionRequestProcessor.java new file mode 100644 index 00000000000..e9633c38deb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectionRequestProcessor.java @@ -0,0 +1,120 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.InjectionRequest; +import org.elasticsearch.util.guice.inject.spi.StaticInjectionRequest; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; +import java.util.Set; + +/** + * Handles {@link Binder#requestInjection} and {@link Binder#requestStaticInjection} commands. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + * @author mikeward@google.com (Mike Ward) + */ +class InjectionRequestProcessor extends AbstractProcessor { + + private final List staticInjections = Lists.newArrayList(); + private final Initializer initializer; + + InjectionRequestProcessor(Errors errors, Initializer initializer) { + super(errors); + this.initializer = initializer; + } + + @Override public Boolean visit(StaticInjectionRequest request) { + staticInjections.add(new StaticInjection(injector, request)); + return true; + } + + @Override public Boolean visit(InjectionRequest request) { + Set injectionPoints; + try { + injectionPoints = request.getInjectionPoints(); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + injectionPoints = e.getPartialValue(); + } + + initializer.requestInjection( + injector, request.getInstance(), request.getSource(), injectionPoints); + return true; + } + + public void validate() { + for (StaticInjection staticInjection : staticInjections) { + staticInjection.validate(); + } + } + + public void injectMembers() { + for (StaticInjection staticInjection : staticInjections) { + staticInjection.injectMembers(); + } + } + + /** A requested static injection. */ + private class StaticInjection { + final InjectorImpl injector; + final Object source; + final StaticInjectionRequest request; + ImmutableList memberInjectors; + + public StaticInjection(InjectorImpl injector, StaticInjectionRequest request) { + this.injector = injector; + this.source = request.getSource(); + this.request = request; + } + + void validate() { + Errors errorsForMember = errors.withSource(source); + Set injectionPoints; + try { + injectionPoints = request.getInjectionPoints(); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + injectionPoints = e.getPartialValue(); + } + memberInjectors = injector.membersInjectorStore.getInjectors( + injectionPoints, errorsForMember); + } + + void injectMembers() { + try { + injector.callInContext(new ContextualCallable() { + public Void call(InternalContext context) { + for (SingleMemberInjector injector : memberInjectors) { + injector.inject(errors, context, null); + } + return null; + } + }); + } catch (ErrorsException e) { + throw new AssertionError(); + } + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Injector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Injector.java new file mode 100644 index 00000000000..b2ad32e6545 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Injector.java @@ -0,0 +1,212 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import java.util.List; +import java.util.Map; + +/** + * Builds the graphs of objects that make up your application. The injector tracks the dependencies + * for each type and uses bindings to inject them. This is the core of Guice, although you rarely + * interact with it directly. This "behind-the-scenes" operation is what distinguishes dependency + * injection from its cousin, the service locator pattern. + * + *

Contains several default bindings: + * + *

    + *
  • This {@link Injector} instance itself + *
  • A {@code Provider} for each binding of type {@code T} + *
  • The {@link java.util.logging.Logger} for the class being injected + *
  • The {@link Stage} in which the Injector was created + *
+ * + * Injectors are created using the facade class {@link Guice}. + * + *

An injector can also {@link #injectMembers(Object) inject the dependencies} of + * already-constructed instances. This can be used to interoperate with objects created by other + * frameworks or services. + * + *

Injectors can be {@link #createChildInjector(Iterable) hierarchical}. Child injectors inherit + * the configuration of their parent injectors, but the converse does not hold. + * + *

The injector's {@link #getBindings() internal bindings} are available for introspection. This + * enables tools and extensions to operate on an injector reflectively. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +public interface Injector { + + /** + * Injects dependencies into the fields and methods of {@code instance}. Ignores the presence or + * absence of an injectable constructor. + * + *

Whenever Guice creates an instance, it performs this injection automatically (after first + * performing constructor injection), so if you're able to let Guice create all your objects for + * you, you'll never need to use this method. + * + * @param instance to inject members on + * + * @see Binder#getMembersInjector(Class) for a preferred alternative that supports checks before + * run time + */ + void injectMembers(Object instance); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. + * + * @param typeLiteral type to get members injector for + * @see Binder#getMembersInjector(TypeLiteral) for an alternative that offers up front error + * detection + * @since 2.0 + */ + MembersInjector getMembersInjector(TypeLiteral typeLiteral); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. When feasible, use {@link Binder#getMembersInjector(TypeLiteral)} + * instead to get increased up front error detection. + * + * @param type type to get members injector for + * @see Binder#getMembersInjector(Class) for an alternative that offers up front error + * detection + * @since 2.0 + */ + MembersInjector getMembersInjector(Class type); + + /** + * Returns all explicit bindings. + * + *

The returned map does not include bindings inherited from a {@link #getParent() parent + * injector}, should one exist. The returned map is guaranteed to iterate (for example, with + * its {@link java.util.Map#entrySet()} iterator) in the order of insertion. In other words, + * the order in which bindings appear in user Modules. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + Map, Binding> getBindings(); + + /** + * Returns the binding for the given injection key. This will be an explicit bindings if the key + * was bound explicitly by a module, or an implicit binding otherwise. The implicit binding will + * be created if necessary. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + * + * @throws ConfigurationException if this injector cannot find or create the binding. + */ + Binding getBinding(Key key); + + /** + * Returns the binding for the given type. This will be an explicit bindings if the injection key + * was bound explicitly by a module, or an implicit binding otherwise. The implicit binding will + * be created if necessary. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + * + * @throws ConfigurationException if this injector cannot find or create the binding. + * @since 2.0 + */ + Binding getBinding(Class type); + + /** + * Returns all explicit bindings for {@code type}. + * + *

This method is part of the Guice SPI and is intended for use by tools and extensions. + */ + List> findBindingsByType(TypeLiteral type); + + /** + * Returns the provider used to obtain instances for the given injection key. When feasible, avoid + * using this method, in favor of having Guice inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @see Binder#getProvider(Key) for an alternative that offers up front error detection + */ + Provider getProvider(Key key); + + /** + * Returns the provider used to obtain instances for the given type. When feasible, avoid + * using this method, in favor of having Guice inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @see Binder#getProvider(Class) for an alternative that offers up front error detection + */ + Provider getProvider(Class type); + + /** + * Returns the appropriate instance for the given injection key; equivalent to {@code + * getProvider(key).get()}. When feasible, avoid using this method, in favor of having Guice + * inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @throws ProvisionException if there was a runtime failure while providing an instance. + */ + T getInstance(Key key); + + /** + * Returns the appropriate instance for the given injection type; equivalent to {@code + * getProvider(type).get()}. When feasible, avoid using this method, in favor of having Guice + * inject your dependencies ahead of time. + * + * @throws ConfigurationException if this injector cannot find or create the provider. + * @throws ProvisionException if there was a runtime failure while providing an instance. + */ + T getInstance(Class type); + + /** + * Returns this injector's parent, or {@code null} if this is a top-level injector. + * + * @since 2.0 + */ + Injector getParent(); + + /** + * Returns a new injector that inherits all state from this injector. All bindings, scopes, + * interceptors and type converters are inherited -- they are visible to the child injector. + * Elements of the child injector are not visible to its parent. + * + *

Just-in-time bindings created for child injectors will be created in an ancestor injector + * whenever possible. This allows for scoped instances to be shared between injectors. Use + * explicit bindings to prevent bindings from being shared with the parent injector. + * + *

No key may be bound by both an injector and one of its ancestors. This includes just-in-time + * bindings. The lone exception is the key for {@code Injector.class}, which is bound by each + * injector to itself. + * + * @since 2.0 + */ + Injector createChildInjector(Iterable modules); + + /** + * Returns a new injector that inherits all state from this injector. All bindings, scopes, + * interceptors and type converters are inherited -- they are visible to the child injector. + * Elements of the child injector are not visible to its parent. + * + *

Just-in-time bindings created for child injectors will be created in an ancestor injector + * whenever possible. This allows for scoped instances to be shared between injectors. Use + * explicit bindings to prevent bindings from being shared with the parent injector. + * + *

No key may be bound by both an injector and one of its ancestors. This includes just-in-time + * bindings. The lone exception is the key for {@code Injector.class}, which is bound by each + * injector to itself. + * + * @since 2.0 + */ + Injector createChildInjector(Module... modules); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorBuilder.java new file mode 100644 index 00000000000..0e0077f5cf0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorBuilder.java @@ -0,0 +1,275 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.Stopwatch; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Iterables; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Builds a tree of injectors. This is a primary injector, plus child injectors needed for each + * {@link Binder#newPrivateBinder() private environment}. The primary injector is not necessarily a + * top-level injector. + * + *

Injector construction happens in two phases. + *

    + *
  1. Static building. In this phase, we interpret commands, create bindings, and inspect + * dependencies. During this phase, we hold a lock to ensure consistency with parent injectors. + * No user code is executed in this phase.
  2. + *
  3. Dynamic injection. In this phase, we call user code. We inject members that requested + * injection. This may require user's objects be created and their providers be called. And we + * create eager singletons. In this phase, user code may have started other threads. This phase + * is not executed for injectors created using {@link Stage#TOOL the tool stage}
  4. + *
+ * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +class InjectorBuilder { + + private final Stopwatch stopwatch = new Stopwatch(); + private final Errors errors = new Errors(); + + private Stage stage; + + private final Initializer initializer = new Initializer(); + private final BindingProcessor bindingProcesor; + private final InjectionRequestProcessor injectionRequestProcessor; + + private final InjectorShell.Builder shellBuilder = new InjectorShell.Builder(); + private List shells; + + InjectorBuilder() { + injectionRequestProcessor = new InjectionRequestProcessor(errors, initializer); + bindingProcesor = new BindingProcessor(errors, initializer); + } + + /** + * Sets the stage for the created injector. If the stage is {@link Stage#PRODUCTION}, this class + * will eagerly load singletons. + */ + InjectorBuilder stage(Stage stage) { + shellBuilder.stage(stage); + this.stage = stage; + return this; + } + + /** + * Sets the parent of the injector to-be-constructed. As a side effect, this sets this injector's + * stage to the stage of {@code parent}. + */ + InjectorBuilder parentInjector(InjectorImpl parent) { + shellBuilder.parent(parent); + return stage(parent.getInstance(Stage.class)); + } + + InjectorBuilder addModules(Iterable modules) { + shellBuilder.addModules(modules); + return this; + } + + Injector build() { + if (shellBuilder == null) { + throw new AssertionError("Already built, builders are not reusable."); + } + + // Synchronize while we're building up the bindings and other injector state. This ensures that + // the JIT bindings in the parent injector don't change while we're being built + synchronized (shellBuilder.lock()) { + shells = shellBuilder.build(initializer, bindingProcesor, stopwatch, errors); + stopwatch.resetAndLog("Injector construction"); + + initializeStatically(); + } + + // If we're in the tool stage, stop here. Don't eagerly inject or load anything. + if (stage == Stage.TOOL) { + return new ToolStageInjector(primaryInjector()); + } + + injectDynamically(); + + return primaryInjector(); + } + + /** Initialize and validate everything. */ + private void initializeStatically() { + bindingProcesor.initializeBindings(); + stopwatch.resetAndLog("Binding initialization"); + + for (InjectorShell shell : shells) { + shell.getInjector().index(); + } + stopwatch.resetAndLog("Binding indexing"); + + injectionRequestProcessor.process(shells); + stopwatch.resetAndLog("Collecting injection requests"); + + bindingProcesor.runCreationListeners(); + stopwatch.resetAndLog("Binding validation"); + + injectionRequestProcessor.validate(); + stopwatch.resetAndLog("Static validation"); + + initializer.validateOustandingInjections(errors); + stopwatch.resetAndLog("Instance member validation"); + + new LookupProcessor(errors).process(shells); + for (InjectorShell shell : shells) { + ((DeferredLookups) shell.getInjector().lookups).initialize(errors); + } + stopwatch.resetAndLog("Provider verification"); + + for (InjectorShell shell : shells) { + if (!shell.getElements().isEmpty()) { + throw new AssertionError("Failed to execute " + shell.getElements()); + } + } + + errors.throwCreationExceptionIfErrorsExist(); + } + + /** + * Returns the injector being constructed. This is not necessarily the root injector. + */ + private Injector primaryInjector() { + return shells.get(0).getInjector(); + } + + /** + * Inject everything that can be injected. This method is intentionally not synchronized. If we + * locked while injecting members (ie. running user code), things would deadlock should the user + * code build a just-in-time binding from another thread. + */ + private void injectDynamically() { + injectionRequestProcessor.injectMembers(); + stopwatch.resetAndLog("Static member injection"); + + initializer.injectAll(errors); + stopwatch.resetAndLog("Instance injection"); + errors.throwCreationExceptionIfErrorsExist(); + + for (InjectorShell shell : shells) { + loadEagerSingletons(shell.getInjector(), stage, errors); + } + stopwatch.resetAndLog("Preloading singletons"); + errors.throwCreationExceptionIfErrorsExist(); + } + + /** + * Loads eager singletons, or all singletons if we're in Stage.PRODUCTION. Bindings discovered + * while we're binding these singletons are not be eager. + */ + public void loadEagerSingletons(InjectorImpl injector, Stage stage, final Errors errors) { + @SuppressWarnings("unchecked") // casting Collection to Collection is safe + Set> candidateBindings = ImmutableSet.copyOf(Iterables.concat( + (Collection) injector.state.getExplicitBindingsThisLevel().values(), + injector.jitBindings.values())); + for (final BindingImpl binding : candidateBindings) { + if (binding.getScoping().isEagerSingleton(stage)) { + try { + injector.callInContext(new ContextualCallable() { + Dependency dependency = Dependency.get(binding.getKey()); + public Void call(InternalContext context) { + context.setDependency(dependency); + Errors errorsForBinding = errors.withSource(dependency); + try { + binding.getInternalFactory().get(errorsForBinding, context, dependency); + } catch (ErrorsException e) { + errorsForBinding.merge(e.getErrors()); + } finally { + context.setDependency(null); + } + + return null; + } + }); + } catch (ErrorsException e) { + throw new AssertionError(); + } + } + } + } + + /** {@link Injector} exposed to users in {@link Stage#TOOL}. */ + static class ToolStageInjector implements Injector { + private final Injector delegateInjector; + + ToolStageInjector(Injector delegateInjector) { + this.delegateInjector = delegateInjector; + } + public void injectMembers(Object o) { + throw new UnsupportedOperationException( + "Injector.injectMembers(Object) is not supported in Stage.TOOL"); + } + public Map, Binding> getBindings() { + return this.delegateInjector.getBindings(); + } + public Binding getBinding(Key key) { + return this.delegateInjector.getBinding(key); + } + public Binding getBinding(Class type) { + return this.delegateInjector.getBinding(type); + } + public List> findBindingsByType(TypeLiteral type) { + return this.delegateInjector.findBindingsByType(type); + } + public Injector getParent() { + return delegateInjector.getParent(); + } + public Injector createChildInjector(Iterable modules) { + return delegateInjector.createChildInjector(modules); + } + public Injector createChildInjector(Module... modules) { + return delegateInjector.createChildInjector(modules); + } + public Provider getProvider(Key key) { + throw new UnsupportedOperationException( + "Injector.getProvider(Key) is not supported in Stage.TOOL"); + } + public Provider getProvider(Class type) { + throw new UnsupportedOperationException( + "Injector.getProvider(Class) is not supported in Stage.TOOL"); + } + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + throw new UnsupportedOperationException( + "Injector.getMembersInjector(TypeLiteral) is not supported in Stage.TOOL"); + } + public MembersInjector getMembersInjector(Class type) { + throw new UnsupportedOperationException( + "Injector.getMembersInjector(Class) is not supported in Stage.TOOL"); + } + public T getInstance(Key key) { + throw new UnsupportedOperationException( + "Injector.getInstance(Key) is not supported in Stage.TOOL"); + } + public T getInstance(Class type) { + throw new UnsupportedOperationException( + "Injector.getInstance(Class) is not supported in Stage.TOOL"); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorImpl.java new file mode 100644 index 00000000000..c2e9f94c03c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorImpl.java @@ -0,0 +1,822 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Annotations; +import static org.elasticsearch.util.guice.inject.internal.Annotations.findScopeAnnotation; +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InstanceBindingImpl; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.internal.LinkedBindingImpl; +import org.elasticsearch.util.guice.inject.internal.LinkedProviderBindingImpl; +import org.elasticsearch.util.guice.inject.internal.MatcherAndConverter; +import org.elasticsearch.util.guice.inject.internal.Nullable; +import org.elasticsearch.util.guice.inject.internal.Scoping; +import org.elasticsearch.util.guice.inject.internal.SourceProvider; +import org.elasticsearch.util.guice.inject.internal.ToStringBuilder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.ConvertedConstantBinding; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.ProviderBinding; +import org.elasticsearch.util.guice.inject.spi.ProviderKeyBinding; +import org.elasticsearch.util.guice.inject.util.Providers; +import org.elasticsearch.util.Classes; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Maps; + +import java.lang.annotation.Annotation; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Default {@link Injector} implementation. + * + * @author crazybob@google.com (Bob Lee) + * @see InjectorBuilder + */ +class InjectorImpl implements Injector, Lookups { + final State state; + final InjectorImpl parent; + final BindingsMultimap bindingsMultimap = new BindingsMultimap(); + final Initializer initializer; + + /** Just-in-time binding cache. Guarded by state.lock() */ + final Map, BindingImpl> jitBindings = Maps.newHashMap(); + + Lookups lookups = new DeferredLookups(this); + + InjectorImpl(@Nullable InjectorImpl parent, State state, Initializer initializer) { + this.parent = parent; + this.state = state; + this.initializer = initializer; + + if (parent != null) { + localContext = parent.localContext; + } else { + localContext = new ThreadLocal() { + protected Object[] initialValue() { + return new Object[1]; + } + }; + } + } + + /** Indexes bindings by type. */ + void index() { + for (Binding binding : state.getExplicitBindingsThisLevel().values()) { + index(binding); + } + } + + void index(Binding binding) { + bindingsMultimap.put(binding.getKey().getTypeLiteral(), binding); + } + + public List> findBindingsByType(TypeLiteral type) { + return bindingsMultimap.getAll(type); + } + + /** Returns the binding for {@code key} */ + public BindingImpl getBinding(Key key) { + Errors errors = new Errors(key); + try { + BindingImpl result = getBindingOrThrow(key, errors); + errors.throwConfigurationExceptionIfErrorsExist(); + return result; + } catch (ErrorsException e) { + throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + } + } + + /** + * Gets a binding implementation. First, it check to see if the parent has a binding. If the + * parent has a binding and the binding is scoped, it will use that binding. Otherwise, this + * checks for an explicit binding. If no explicit binding is found, it looks for a just-in-time + * binding. + */ + public BindingImpl getBindingOrThrow(Key key, Errors errors) + throws ErrorsException { + // Check explicit bindings, i.e. bindings created by modules. + BindingImpl binding = state.getExplicitBinding(key); + if (binding != null) { + return binding; + } + + // Look for an on-demand binding. + return getJustInTimeBinding(key, errors); + } + + public Binding getBinding(Class type) { + return getBinding(Key.get(type)); + } + + public Injector getParent() { + return parent; + } + + public Injector createChildInjector(Iterable modules) { + return new InjectorBuilder() + .parentInjector(this) + .addModules(modules) + .build(); + } + + public Injector createChildInjector(Module... modules) { + return createChildInjector(ImmutableList.of(modules)); + } + + /** + * Returns a just-in-time binding for {@code key}, creating it if necessary. + * + * @throws ErrorsException if the binding could not be created. + */ + private BindingImpl getJustInTimeBinding(Key key, Errors errors) + throws ErrorsException { + synchronized (state.lock()) { + // first try to find a JIT binding that we've already created + for (InjectorImpl injector = this; injector != null; injector = injector.parent) { + @SuppressWarnings("unchecked") // we only store bindings that match their key + BindingImpl binding = (BindingImpl) injector.jitBindings.get(key); + + if (binding != null) { + return binding; + } + } + + return createJustInTimeBindingRecursive(key, errors); + } + } + + /** Returns true if the key type is Provider (but not a subclass of Provider). */ + static boolean isProvider(Key key) { + return key.getTypeLiteral().getRawType().equals(Provider.class); + } + + /** Returns true if the key type is MembersInjector (but not a subclass of MembersInjector). */ + static boolean isMembersInjector(Key key) { + return key.getTypeLiteral().getRawType().equals(MembersInjector.class) + && !key.hasAnnotationType(); + } + + private BindingImpl> createMembersInjectorBinding( + Key> key, Errors errors) throws ErrorsException { + Type membersInjectorType = key.getTypeLiteral().getType(); + if (!(membersInjectorType instanceof ParameterizedType)) { + throw errors.cannotInjectRawMembersInjector().toException(); + } + + @SuppressWarnings("unchecked") // safe because T came from Key> + TypeLiteral instanceType = (TypeLiteral) TypeLiteral.get( + ((ParameterizedType) membersInjectorType).getActualTypeArguments()[0]); + MembersInjector membersInjector = membersInjectorStore.get(instanceType, errors); + + InternalFactory> factory = new ConstantFactory>( + Initializables.of(membersInjector)); + + + return new InstanceBindingImpl>(this, key, SourceProvider.UNKNOWN_SOURCE, + factory, ImmutableSet.of(), membersInjector); + } + + /** + * Creates a synthetic binding to {@code Provider}, i.e. a binding to the provider from + * {@code Binding}. + */ + private BindingImpl> createProviderBinding(Key> key, Errors errors) + throws ErrorsException { + Type providerType = key.getTypeLiteral().getType(); + + // If the Provider has no type parameter (raw Provider)... + if (!(providerType instanceof ParameterizedType)) { + throw errors.cannotInjectRawProvider().toException(); + } + + Type entryType = ((ParameterizedType) providerType).getActualTypeArguments()[0]; + + @SuppressWarnings("unchecked") // safe because T came from Key> + Key providedKey = (Key) key.ofType(entryType); + + BindingImpl delegate = getBindingOrThrow(providedKey, errors); + return new ProviderBindingImpl(this, key, delegate); + } + + static class ProviderBindingImpl extends BindingImpl> + implements ProviderBinding> { + final BindingImpl providedBinding; + + ProviderBindingImpl(InjectorImpl injector, Key> key, Binding providedBinding) { + super(injector, key, providedBinding.getSource(), createInternalFactory(providedBinding), + Scoping.UNSCOPED); + this.providedBinding = (BindingImpl) providedBinding; + } + + static InternalFactory> createInternalFactory(Binding providedBinding) { + final Provider provider = providedBinding.getProvider(); + return new InternalFactory>() { + public Provider get(Errors errors, InternalContext context, Dependency dependency) { + return provider; + } + }; + } + + public Key getProvidedKey() { + return providedBinding.getKey(); + } + + public V acceptTargetVisitor(BindingTargetVisitor, V> visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } + + @Override public String toString() { + return new ToStringBuilder(ProviderKeyBinding.class) + .add("key", getKey()) + .add("providedKey", getProvidedKey()) + .toString(); + } + } + + /** + * Converts a constant string binding to the required type. + * + * @return the binding if it could be resolved, or null if the binding doesn't exist + * @throws org.elasticsearch.util.guice.inject.internal.ErrorsException if there was an error resolving the binding + */ + private BindingImpl convertConstantStringBinding(Key key, Errors errors) + throws ErrorsException { + // Find a constant string binding. + Key stringKey = key.ofType(String.class); + BindingImpl stringBinding = state.getExplicitBinding(stringKey); + if (stringBinding == null || !stringBinding.isConstant()) { + return null; + } + + String stringValue = stringBinding.getProvider().get(); + Object source = stringBinding.getSource(); + + // Find a matching type converter. + TypeLiteral type = key.getTypeLiteral(); + MatcherAndConverter matchingConverter = state.getConverter(stringValue, type, errors, source); + + if (matchingConverter == null) { + // No converter can handle the given type. + return null; + } + + // Try to convert the string. A failed conversion results in an error. + try { + @SuppressWarnings("unchecked") // This cast is safe because we double check below. + T converted = (T) matchingConverter.getTypeConverter().convert(stringValue, type); + + if (converted == null) { + throw errors.converterReturnedNull(stringValue, source, type, matchingConverter) + .toException(); + } + + if (!type.getRawType().isInstance(converted)) { + throw errors.conversionTypeError(stringValue, source, type, matchingConverter, converted) + .toException(); + } + + return new ConvertedConstantBindingImpl(this, key, converted, stringBinding); + } catch (ErrorsException e) { + throw e; + } catch (RuntimeException e) { + throw errors.conversionError(stringValue, source, type, matchingConverter, e) + .toException(); + } + } + + private static class ConvertedConstantBindingImpl + extends BindingImpl implements ConvertedConstantBinding { + final T value; + final Provider provider; + final Binding originalBinding; + + ConvertedConstantBindingImpl( + Injector injector, Key key, T value, Binding originalBinding) { + super(injector, key, originalBinding.getSource(), + new ConstantFactory(Initializables.of(value)), Scoping.UNSCOPED); + this.value = value; + provider = Providers.of(value); + this.originalBinding = originalBinding; + } + + @Override public Provider getProvider() { + return provider; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public T getValue() { + return value; + } + + public Key getSourceKey() { + return originalBinding.getKey(); + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(getSourceKey())); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } + + @Override public String toString() { + return new ToStringBuilder(ConvertedConstantBinding.class) + .add("key", getKey()) + .add("sourceKey", getSourceKey()) + .add("value", value) + .toString(); + } + } + + void initializeBinding(BindingImpl binding, Errors errors) throws ErrorsException { + // Put the partially constructed binding in the map a little early. This enables us to handle + // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl. + // Note: We don't need to synchronize on state.lock() during injector creation. + // TODO: for the above example, remove the binding for BarImpl if the binding for FooImpl fails + if (binding instanceof ConstructorBindingImpl) { + Key key = binding.getKey(); + jitBindings.put(key, binding); + boolean successful = false; + try { + ((ConstructorBindingImpl) binding).initialize(this, errors); + successful = true; + } finally { + if (!successful) { + jitBindings.remove(key); + } + } + } + } + + /** + * Creates a binding for an injectable type with the given scope. Looks for a scope on the type if + * none is specified. + */ + BindingImpl createUnitializedBinding(Key key, Scoping scoping, Object source, + Errors errors) throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + + // Don't try to inject arrays, or enums. + if (rawType.isArray() || rawType.isEnum()) { + throw errors.missingImplementation(key).toException(); + } + + // Handle TypeLiteral by binding the inner type + if (rawType == TypeLiteral.class) { + @SuppressWarnings("unchecked") // we have to fudge the inner type as Object + BindingImpl binding = (BindingImpl) createTypeLiteralBinding( + (Key>) key, errors); + return binding; + } + + // Handle @ImplementedBy + ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class); + if (implementedBy != null) { + Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); + return createImplementedByBinding(key, scoping, implementedBy, errors); + } + + // Handle @ProvidedBy. + ProvidedBy providedBy = rawType.getAnnotation(ProvidedBy.class); + if (providedBy != null) { + Annotations.checkForMisplacedScopeAnnotations(rawType, source, errors); + return createProvidedByBinding(key, scoping, providedBy, errors); + } + + // We can't inject abstract classes. + // TODO: Method interceptors could actually enable us to implement + // abstract types. Should we remove this restriction? + if (Modifier.isAbstract(rawType.getModifiers())) { + throw errors.missingImplementation(key).toException(); + } + + // Error: Inner class. + if (Classes.isInnerClass(rawType)) { + throw errors.cannotInjectInnerClass(rawType).toException(); + } + + if (!scoping.isExplicitlyScoped()) { + Class scopeAnnotation = findScopeAnnotation(errors, rawType); + if (scopeAnnotation != null) { + scoping = Scopes.makeInjectable(Scoping.forAnnotation(scopeAnnotation), + this, errors.withSource(rawType)); + } + } + + return ConstructorBindingImpl.create(this, key, source, scoping); + } + + /** + * Converts a binding for a {@code Key>} to the value {@code TypeLiteral}. It's + * a bit awkward because we have to pull out the inner type in the type literal. + */ + private BindingImpl> createTypeLiteralBinding( + Key> key, Errors errors) throws ErrorsException { + Type typeLiteralType = key.getTypeLiteral().getType(); + if (!(typeLiteralType instanceof ParameterizedType)) { + throw errors.cannotInjectRawTypeLiteral().toException(); + } + + ParameterizedType parameterizedType = (ParameterizedType) typeLiteralType; + Type innerType = parameterizedType.getActualTypeArguments()[0]; + + // this is unforunate. We don't support building TypeLiterals for type variable like 'T'. If + // this proves problematic, we can probably fix TypeLiteral to support type variables + if (!(innerType instanceof Class) + && !(innerType instanceof GenericArrayType) + && !(innerType instanceof ParameterizedType)) { + throw errors.cannotInjectTypeLiteralOf(innerType).toException(); + } + + @SuppressWarnings("unchecked") // by definition, innerType == T, so this is safe + TypeLiteral value = (TypeLiteral) TypeLiteral.get(innerType); + InternalFactory> factory = new ConstantFactory>( + Initializables.of(value)); + return new InstanceBindingImpl>(this, key, SourceProvider.UNKNOWN_SOURCE, + factory, ImmutableSet.of(), value); + } + + /** Creates a binding for a type annotated with @ProvidedBy. */ + BindingImpl createProvidedByBinding(Key key, Scoping scoping, + ProvidedBy providedBy, Errors errors) throws ErrorsException { + final Class rawType = key.getTypeLiteral().getRawType(); + final Class> providerType = providedBy.value(); + + // Make sure it's not the same type. TODO: Can we check for deeper loops? + if (providerType == rawType) { + throw errors.recursiveProviderType().toException(); + } + + // Assume the provider provides an appropriate type. We double check at runtime. + @SuppressWarnings("unchecked") + final Key> providerKey + = (Key>) Key.get(providerType); + final BindingImpl> providerBinding + = getBindingOrThrow(providerKey, errors); + + InternalFactory internalFactory = new InternalFactory() { + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + errors = errors.withSource(providerKey); + Provider provider = providerBinding.getInternalFactory().get( + errors, context, dependency); + try { + Object o = provider.get(); + if (o != null && !rawType.isInstance(o)) { + throw errors.subtypeNotProvided(providerType, rawType).toException(); + } + @SuppressWarnings("unchecked") // protected by isInstance() check above + T t = (T) o; + return t; + } catch (RuntimeException e) { + throw errors.errorInProvider(e).toException(); + } + } + }; + + return new LinkedProviderBindingImpl( + this, + key, + rawType /* source */, + Scopes.scope(key, this, internalFactory, scoping), + scoping, + providerKey); + } + + /** Creates a binding for a type annotated with @ImplementedBy. */ + BindingImpl createImplementedByBinding(Key key, Scoping scoping, + ImplementedBy implementedBy, Errors errors) + throws ErrorsException { + Class rawType = key.getTypeLiteral().getRawType(); + Class implementationType = implementedBy.value(); + + // Make sure it's not the same type. TODO: Can we check for deeper cycles? + if (implementationType == rawType) { + throw errors.recursiveImplementationType().toException(); + } + + // Make sure implementationType extends type. + if (!rawType.isAssignableFrom(implementationType)) { + throw errors.notASubtype(implementationType, rawType).toException(); + } + + @SuppressWarnings("unchecked") // After the preceding check, this cast is safe. + Class subclass = (Class) implementationType; + + // Look up the target binding. + final Key targetKey = Key.get(subclass); + final BindingImpl targetBinding = getBindingOrThrow(targetKey, errors); + + InternalFactory internalFactory = new InternalFactory() { + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + return targetBinding.getInternalFactory().get( + errors.withSource(targetKey), context, dependency); + } + }; + + return new LinkedBindingImpl( + this, + key, + rawType /* source */, + Scopes.scope(key, this, internalFactory, scoping), + scoping, + targetKey); + } + + /** + * Attempts to create a just-in-time binding for {@code key} in the root injector, falling back to + * other ancestor injectors until this injector is tried. + */ + private BindingImpl createJustInTimeBindingRecursive(Key key, Errors errors) + throws ErrorsException { + // ask the parent to create the JIT binding + if (parent != null) { + try { + return parent.createJustInTimeBindingRecursive(key, new Errors()); + } catch (ErrorsException ignored) { + } + } + + if (state.isBlacklisted(key)) { + throw errors.childBindingAlreadySet(key).toException(); + } + + BindingImpl binding = createJustInTimeBinding(key, errors); + state.parent().blacklist(key); + jitBindings.put(key, binding); + return binding; + } + + /** + * Returns a new just-in-time binding created by resolving {@code key}. The strategies used to + * create just-in-time bindings are: + *
    + *
  1. Internalizing Providers. If the requested binding is for {@code Provider}, we delegate + * to the binding for {@code T}. + *
  2. Converting constants. + *
  3. ImplementedBy and ProvidedBy annotations. Only for unannotated keys. + *
  4. The constructor of the raw type. Only for unannotated keys. + *
+ * + * @throws org.elasticsearch.util.guice.inject.internal.ErrorsException if the binding cannot be created. + */ + BindingImpl createJustInTimeBinding(Key key, Errors errors) throws ErrorsException { + if (state.isBlacklisted(key)) { + throw errors.childBindingAlreadySet(key).toException(); + } + + // Handle cases where T is a Provider. + if (isProvider(key)) { + // These casts are safe. We know T extends Provider and that given Key>, + // createProviderBinding() will return BindingImpl>. + @SuppressWarnings("unchecked") + BindingImpl binding = createProviderBinding((Key) key, errors); + return binding; + } + + // Handle cases where T is a MembersInjector + if (isMembersInjector(key)) { + // These casts are safe. T extends MembersInjector and that given Key>, + // createMembersInjectorBinding() will return BindingImpl>. + @SuppressWarnings("unchecked") + BindingImpl binding = createMembersInjectorBinding((Key) key, errors); + return binding; + } + + // Try to convert a constant string binding to the requested type. + BindingImpl convertedBinding = convertConstantStringBinding(key, errors); + if (convertedBinding != null) { + return convertedBinding; + } + + // If the key has an annotation... + if (key.hasAnnotationType()) { + // Look for a binding without annotation attributes or return null. + if (key.hasAttributes()) { + try { + Errors ignored = new Errors(); + return getBindingOrThrow(key.withoutAttributes(), ignored); + } catch (ErrorsException ignored) { + // throw with a more appropriate message below + } + } + throw errors.missingImplementation(key).toException(); + } + + Object source = key.getTypeLiteral().getRawType(); + BindingImpl binding = createUnitializedBinding(key, Scoping.UNSCOPED, source, errors); + initializeBinding(binding, errors); + return binding; + } + + InternalFactory getInternalFactory(Key key, Errors errors) + throws ErrorsException { + return getBindingOrThrow(key, errors).getInternalFactory(); + } + + // not test-covered + public Map, Binding> getBindings() { + return state.getExplicitBindingsThisLevel(); + } + + private static class BindingsMultimap { + final Map, List>> multimap = Maps.newHashMap(); + + void put(TypeLiteral type, Binding binding) { + List> bindingsForType = multimap.get(type); + if (bindingsForType == null) { + bindingsForType = Lists.newArrayList(); + multimap.put(type, bindingsForType); + } + bindingsForType.add(binding); + } + + + @SuppressWarnings("unchecked") // safe because we only put matching entries into the map + List> getAll(TypeLiteral type) { + List> bindings = multimap.get(type); + return bindings != null + ? Collections.>unmodifiableList((List) multimap.get(type)) + : ImmutableList.>of(); + } + } + + /** + * Returns parameter injectors, or {@code null} if there are no parameters. + */ + SingleParameterInjector[] getParametersInjectors( + List> parameters, Errors errors) throws ErrorsException { + if (parameters.isEmpty()) { + return null; + } + + int numErrorsBefore = errors.size(); + SingleParameterInjector[] result = new SingleParameterInjector[parameters.size()]; + int i = 0; + for (Dependency parameter : parameters) { + try { + result[i++] = createParameterInjector(parameter, errors.withSource(parameter)); + } catch (ErrorsException rethrownBelow) { + // rethrown below + } + } + + errors.throwIfNewErrors(numErrorsBefore); + return result; + } + + SingleParameterInjector createParameterInjector(final Dependency dependency, + final Errors errors) throws ErrorsException { + InternalFactory factory = getInternalFactory(dependency.getKey(), errors); + return new SingleParameterInjector(dependency, factory); + } + + /** Invokes a method. */ + interface MethodInvoker { + Object invoke(Object target, Object... parameters) + throws IllegalAccessException, InvocationTargetException; + } + + /** Cached constructor injectors for each type */ + final ConstructorInjectorStore constructors = new ConstructorInjectorStore(this); + + /** Cached field and method injectors for each type. */ + MembersInjectorStore membersInjectorStore; + + @SuppressWarnings("unchecked") // the members injector type is consistent with instance's type + public void injectMembers(Object instance) { + MembersInjector membersInjector = getMembersInjector(instance.getClass()); + membersInjector.injectMembers(instance); + } + + public MembersInjector getMembersInjector(TypeLiteral typeLiteral) { + Errors errors = new Errors(typeLiteral); + try { + return membersInjectorStore.get(typeLiteral, errors); + } catch (ErrorsException e) { + throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + } + } + + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } + + public Provider getProvider(Class type) { + return getProvider(Key.get(type)); + } + + Provider getProviderOrThrow(final Key key, Errors errors) throws ErrorsException { + final InternalFactory factory = getInternalFactory(key, errors); + final Dependency dependency = Dependency.get(key); + + return new Provider() { + public T get() { + final Errors errors = new Errors(dependency); + try { + T t = callInContext(new ContextualCallable() { + public T call(InternalContext context) throws ErrorsException { + context.setDependency(dependency); + try { + return factory.get(errors, context, dependency); + } finally { + context.setDependency(null); + } + } + }); + errors.throwIfNewErrors(0); + return t; + } catch (ErrorsException e) { + throw new ProvisionException(errors.merge(e.getErrors()).getMessages()); + } + } + + @Override public String toString() { + return factory.toString(); + } + }; + } + + public Provider getProvider(final Key key) { + Errors errors = new Errors(key); + try { + Provider result = getProviderOrThrow(key, errors); + errors.throwIfNewErrors(0); + return result; + } catch (ErrorsException e) { + throw new ConfigurationException(errors.merge(e.getErrors()).getMessages()); + } + } + + public T getInstance(Key key) { + return getProvider(key).get(); + } + + public T getInstance(Class type) { + return getProvider(type).get(); + } + + final ThreadLocal localContext; + + /** Looks up thread local context. Creates (and removes) a new context if necessary. */ + T callInContext(ContextualCallable callable) throws ErrorsException { + Object[] reference = localContext.get(); + if (reference[0] == null) { + reference[0] = new InternalContext(); + try { + return callable.call((InternalContext)reference[0]); + } finally { + // Only clear the context if this call created it. + reference[0] = null; + } + } else { + // Someone else will clean up this context. + return callable.call((InternalContext)reference[0]); + } + } + + public String toString() { + return new ToStringBuilder(Injector.class) + .add("bindings", state.getExplicitBindingsThisLevel().values()) + .toString(); + } + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorShell.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorShell.java new file mode 100644 index 00000000000..b9fede9904e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InjectorShell.java @@ -0,0 +1,256 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import static org.elasticsearch.util.guice.inject.Scopes.SINGLETON; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.internal.PrivateElementsImpl; +import org.elasticsearch.util.guice.inject.internal.ProviderInstanceBindingImpl; +import org.elasticsearch.util.guice.inject.internal.Scoping; +import org.elasticsearch.util.guice.inject.internal.SourceProvider; +import org.elasticsearch.util.guice.inject.internal.Stopwatch; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.Elements; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; +import java.util.logging.Logger; + +/** + * A partially-initialized injector. See {@link InjectorBuilder}, which uses this to build a tree + * of injectors in batch. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class InjectorShell { + + private final List elements; + private final InjectorImpl injector; + private final PrivateElements privateElements; + + private InjectorShell(Builder builder, List elements, InjectorImpl injector) { + this.privateElements = builder.privateElements; + this.elements = elements; + this.injector = injector; + } + + PrivateElements getPrivateElements() { + return privateElements; + } + + InjectorImpl getInjector() { + return injector; + } + + List getElements() { + return elements; + } + + static class Builder { + private final List elements = Lists.newArrayList(); + private final List modules = Lists.newArrayList(); + + /** lazily constructed */ + private State state; + + private InjectorImpl parent; + private Stage stage; + + /** null unless this exists in a {@link Binder#newPrivateBinder private environment} */ + private PrivateElementsImpl privateElements; + + Builder parent(InjectorImpl parent) { + this.parent = parent; + this.state = new InheritingState(parent.state); + return this; + } + + Builder stage(Stage stage) { + this.stage = stage; + return this; + } + + Builder privateElements(PrivateElements privateElements) { + this.privateElements = (PrivateElementsImpl) privateElements; + this.elements.addAll(privateElements.getElements()); + return this; + } + + void addModules(Iterable modules) { + for (Module module : modules) { + this.modules.add(module); + } + } + + /** Synchronize on this before calling {@link #build}. */ + Object lock() { + return getState().lock(); + } + + /** + * Creates and returns the injector shells for the current modules. Multiple shells will be + * returned if any modules contain {@link Binder#newPrivateBinder private environments}. The + * primary injector will be first in the returned list. + */ + List build(Initializer initializer, BindingProcessor bindingProcessor, + Stopwatch stopwatch, Errors errors) { + checkState(stage != null, "Stage not initialized"); + checkState(privateElements == null || parent != null, "PrivateElements with no parent"); + checkState(state != null, "no state. Did you remember to lock() ?"); + + InjectorImpl injector = new InjectorImpl(parent, state, initializer); + if (privateElements != null) { + privateElements.initInjector(injector); + } + + // bind Stage and Singleton if this is a top-level injector + if (parent == null) { + modules.add(0, new RootModule(stage)); + new TypeConverterBindingProcessor(errors).prepareBuiltInConverters(injector); + } + + elements.addAll(Elements.getElements(stage, modules)); + stopwatch.resetAndLog("Module execution"); + + new MessageProcessor(errors).process(injector, elements); + + new TypeListenerBindingProcessor(errors).process(injector, elements); + List listenerBindings = injector.state.getTypeListenerBindings(); + injector.membersInjectorStore = new MembersInjectorStore(injector, listenerBindings); + stopwatch.resetAndLog("TypeListeners creation"); + + new ScopeBindingProcessor(errors).process(injector, elements); + stopwatch.resetAndLog("Scopes creation"); + + new TypeConverterBindingProcessor(errors).process(injector, elements); + stopwatch.resetAndLog("Converters creation"); + + bindInjector(injector); + bindLogger(injector); + bindingProcessor.process(injector, elements); + stopwatch.resetAndLog("Binding creation"); + + List injectorShells = Lists.newArrayList(); + injectorShells.add(new InjectorShell(this, elements, injector)); + + // recursively build child shells + PrivateElementProcessor processor = new PrivateElementProcessor(errors, stage); + processor.process(injector, elements); + for (Builder builder : processor.getInjectorShellBuilders()) { + injectorShells.addAll(builder.build(initializer, bindingProcessor, stopwatch, errors)); + } + stopwatch.resetAndLog("Private environment creation"); + + return injectorShells; + } + + private State getState() { + if (state == null) { + state = new InheritingState(State.NONE); + } + return state; + } + } + + /** + * The Injector is a special case because we allow both parent and child injectors to both have + * a binding for that key. + */ + private static void bindInjector(InjectorImpl injector) { + Key key = Key.get(Injector.class); + InjectorFactory injectorFactory = new InjectorFactory(injector); + injector.state.putBinding(key, + new ProviderInstanceBindingImpl(injector, key, SourceProvider.UNKNOWN_SOURCE, + injectorFactory, Scoping.UNSCOPED, injectorFactory, + ImmutableSet.of())); + } + + private static class InjectorFactory implements InternalFactory, Provider { + private final Injector injector; + + private InjectorFactory(Injector injector) { + this.injector = injector; + } + + public Injector get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + return injector; + } + + public Injector get() { + return injector; + } + + public String toString() { + return "Provider"; + } + } + + /** + * The Logger is a special case because it knows the injection point of the injected member. It's + * the only binding that does this. + */ + private static void bindLogger(InjectorImpl injector) { + Key key = Key.get(Logger.class); + LoggerFactory loggerFactory = new LoggerFactory(); + injector.state.putBinding(key, + new ProviderInstanceBindingImpl(injector, key, + SourceProvider.UNKNOWN_SOURCE, loggerFactory, Scoping.UNSCOPED, + loggerFactory, ImmutableSet.of())); + } + + private static class LoggerFactory implements InternalFactory, Provider { + public Logger get(Errors errors, InternalContext context, Dependency dependency) { + InjectionPoint injectionPoint = dependency.getInjectionPoint(); + return injectionPoint == null + ? Logger.getAnonymousLogger() + : Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); + } + + public Logger get() { + return Logger.getAnonymousLogger(); + } + + public String toString() { + return "Provider"; + } + } + + private static class RootModule implements Module { + final Stage stage; + + private RootModule(Stage stage) { + this.stage = checkNotNull(stage, "stage"); + } + + public void configure(Binder binder) { + binder = binder.withSource(SourceProvider.UNKNOWN_SOURCE); + binder.bind(Stage.class).toInstance(stage); + binder.bindScope(Singleton.class, SINGLETON); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InternalFactoryToProviderAdapter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InternalFactoryToProviderAdapter.java new file mode 100644 index 00000000000..73a46c1a739 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/InternalFactoryToProviderAdapter.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.internal.SourceProvider; +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * @author crazybob@google.com (Bob Lee) +*/ +class InternalFactoryToProviderAdapter implements InternalFactory { + + private final Initializable> initializable; + private final Object source; + + public InternalFactoryToProviderAdapter(Initializable> initializable) { + this(initializable, SourceProvider.UNKNOWN_SOURCE); + } + + public InternalFactoryToProviderAdapter( + Initializable> initializable, Object source) { + this.initializable = checkNotNull(initializable, "provider"); + this.source = checkNotNull(source, "source"); + } + + public T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException { + try { + return errors.checkForNull(initializable.get(errors).get(), source, dependency); + } catch (RuntimeException userException) { + throw errors.withSource(source).errorInProvider(userException).toException(); + } + } + + @Override public String toString() { + return initializable.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Key.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Key.java new file mode 100644 index 00000000000..5da902e5eff --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Key.java @@ -0,0 +1,494 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Annotations; +import org.elasticsearch.util.guice.inject.internal.MoreTypes; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.internal.ToStringBuilder; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +/** + * Binding key consisting of an injection type and an optional annotation. + * Matches the type and annotation at a point of injection. + * + *

For example, {@code Key.get(Service.class, Transactional.class)} will + * match: + * + *

+ *   {@literal @}Inject
+ *   public void setService({@literal @}Transactional Service service) {
+ *     ...
+ *   }
+ * 
+ * + *

{@code Key} supports generic types via subclassing just like {@link + * TypeLiteral}. + * + *

Keys do not differentiate between primitive types (int, char, etc.) and + * their correpsonding wrapper types (Integer, Character, etc.). Primitive + * types will be replaced with their wrapper types when keys are created. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Key { + + private final AnnotationStrategy annotationStrategy; + + private final TypeLiteral typeLiteral; + private final int hashCode; + + /** + * Constructs a new key. Derives the type from this class's type parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + * + *

Example usage for a binding of type {@code Foo} annotated with + * {@code @Bar}: + * + *

{@code new Key(Bar.class) {}}. + */ + @SuppressWarnings("unchecked") + protected Key(Class annotationType) { + this.annotationStrategy = strategyFor(annotationType); + this.typeLiteral = (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass()); + this.hashCode = computeHashCode(); + } + + /** + * Constructs a new key. Derives the type from this class's type parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + * + *

Example usage for a binding of type {@code Foo} annotated with + * {@code @Bar}: + * + *

{@code new Key(new Bar()) {}}. + */ + @SuppressWarnings("unchecked") + protected Key(Annotation annotation) { + // no usages, not test-covered + this.annotationStrategy = strategyFor(annotation); + this.typeLiteral = (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass()); + this.hashCode = computeHashCode(); + } + + /** + * Constructs a new key. Derives the type from this class's type parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + * + *

Example usage for a binding of type {@code Foo}: + * + *

{@code new Key() {}}. + */ + @SuppressWarnings("unchecked") + protected Key() { + this.annotationStrategy = NullAnnotationStrategy.INSTANCE; + this.typeLiteral = (TypeLiteral) TypeLiteral.fromSuperclassTypeParameter(getClass()); + this.hashCode = computeHashCode(); + } + + /** + * Unsafe. Constructs a key from a manually specified type. + */ + @SuppressWarnings("unchecked") + private Key(Type type, AnnotationStrategy annotationStrategy) { + this.annotationStrategy = annotationStrategy; + this.typeLiteral = MoreTypes.makeKeySafe((TypeLiteral) TypeLiteral.get(type)); + this.hashCode = computeHashCode(); + } + + /** Constructs a key from a manually specified type. */ + private Key(TypeLiteral typeLiteral, AnnotationStrategy annotationStrategy) { + this.annotationStrategy = annotationStrategy; + this.typeLiteral = MoreTypes.makeKeySafe(typeLiteral); + this.hashCode = computeHashCode(); + } + + private int computeHashCode() { + return typeLiteral.hashCode() * 31 + annotationStrategy.hashCode(); + } + + /** + * Gets the key type. + */ + public final TypeLiteral getTypeLiteral() { + return typeLiteral; + } + + /** + * Gets the annotation type. + */ + public final Class getAnnotationType() { + return annotationStrategy.getAnnotationType(); + } + + /** + * Gets the annotation. + */ + public final Annotation getAnnotation() { + return annotationStrategy.getAnnotation(); + } + + boolean hasAnnotationType() { + return annotationStrategy.getAnnotationType() != null; + } + + String getAnnotationName() { + Annotation annotation = annotationStrategy.getAnnotation(); + if (annotation != null) { + return annotation.toString(); + } + + // not test-covered + return annotationStrategy.getAnnotationType().toString(); + } + + Class getRawType() { + return typeLiteral.getRawType(); + } + + /** + * Gets the key of this key's provider. + */ + Key> providerKey() { + return ofType(typeLiteral.providerType()); + } + + @Override public final boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Key)) { + return false; + } + Key other = (Key) o; + return annotationStrategy.equals(other.annotationStrategy) + && typeLiteral.equals(other.typeLiteral); + } + + @Override public final int hashCode() { + return this.hashCode; + } + + @Override public final String toString() { + return new ToStringBuilder(Key.class) + .add("type", typeLiteral) + .add("annotation", annotationStrategy) + .toString(); + } + + /** + * Gets a key for an injection type and an annotation strategy. + */ + static Key get(Class type, + AnnotationStrategy annotationStrategy) { + return new Key(type, annotationStrategy); + } + + /** + * Gets a key for an injection type. + */ + public static Key get(Class type) { + return new Key(type, NullAnnotationStrategy.INSTANCE); + } + + /** + * Gets a key for an injection type and an annotation type. + */ + public static Key get(Class type, + Class annotationType) { + return new Key(type, strategyFor(annotationType)); + } + + /** + * Gets a key for an injection type and an annotation. + */ + public static Key get(Class type, Annotation annotation) { + return new Key(type, strategyFor(annotation)); + } + + /** + * Gets a key for an injection type. + */ + public static Key get(Type type) { + return new Key(type, NullAnnotationStrategy.INSTANCE); + } + + /** + * Gets a key for an injection type and an annotation type. + */ + public static Key get(Type type, + Class annotationType) { + return new Key(type, strategyFor(annotationType)); + } + + /** + * Gets a key for an injection type and an annotation. + */ + public static Key get(Type type, Annotation annotation) { + return new Key(type, strategyFor(annotation)); + } + + /** + * Gets a key for an injection type. + */ + public static Key get(TypeLiteral typeLiteral) { + return new Key(typeLiteral, NullAnnotationStrategy.INSTANCE); + } + + /** + * Gets a key for an injection type and an annotation type. + */ + public static Key get(TypeLiteral typeLiteral, + Class annotationType) { + return new Key(typeLiteral, strategyFor(annotationType)); + } + + /** + * Gets a key for an injection type and an annotation. + */ + public static Key get(TypeLiteral typeLiteral, + Annotation annotation) { + return new Key(typeLiteral, strategyFor(annotation)); + } + + /** + * Returns a new key of the specified type with the same annotation as this + * key. + */ + Key ofType(Class type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns a new key of the specified type with the same annotation as this + * key. + */ + Key ofType(Type type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns a new key of the specified type with the same annotation as this + * key. + */ + Key ofType(TypeLiteral type) { + return new Key(type, annotationStrategy); + } + + /** + * Returns true if this key has annotation attributes. + */ + boolean hasAttributes() { + return annotationStrategy.hasAttributes(); + } + + /** + * Returns this key without annotation attributes, i.e. with only the + * annotation type. + */ + Key withoutAttributes() { + return new Key(typeLiteral, annotationStrategy.withoutAttributes()); + } + + interface AnnotationStrategy { + Annotation getAnnotation(); + Class getAnnotationType(); + boolean hasAttributes(); + AnnotationStrategy withoutAttributes(); + } + + /** + * Returns {@code true} if the given annotation type has no attributes. + */ + static boolean isMarker(Class annotationType) { + return annotationType.getDeclaredMethods().length == 0; + } + + /** + * Gets the strategy for an annotation. + */ + static AnnotationStrategy strategyFor(Annotation annotation) { + checkNotNull(annotation, "annotation"); + Class annotationType = annotation.annotationType(); + ensureRetainedAtRuntime(annotationType); + ensureIsBindingAnnotation(annotationType); + + if (annotationType.getDeclaredMethods().length == 0) { + return new AnnotationTypeStrategy(annotationType, annotation); + } + + return new AnnotationInstanceStrategy(annotation); + } + + /** + * Gets the strategy for an annotation type. + */ + static AnnotationStrategy strategyFor(Class annotationType) { + checkNotNull(annotationType, "annotation type"); + ensureRetainedAtRuntime(annotationType); + ensureIsBindingAnnotation(annotationType); + return new AnnotationTypeStrategy(annotationType, null); + } + + private static void ensureRetainedAtRuntime( + Class annotationType) { + checkArgument(Annotations.isRetainedAtRuntime(annotationType), + "%s is not retained at runtime. Please annotate it with @Retention(RUNTIME).", + annotationType.getName()); + } + + private static void ensureIsBindingAnnotation( + Class annotationType) { + checkArgument(isBindingAnnotation(annotationType), + "%s is not a binding annotation. Please annotate it with @BindingAnnotation.", + annotationType.getName()); + } + + static enum NullAnnotationStrategy implements AnnotationStrategy { + INSTANCE; + + public boolean hasAttributes() { + return false; + } + + public AnnotationStrategy withoutAttributes() { + throw new UnsupportedOperationException("Key already has no attributes."); + } + + public Annotation getAnnotation() { + return null; + } + + public Class getAnnotationType() { + return null; + } + + @Override public String toString() { + return "[none]"; + } + } + + // this class not test-covered + static class AnnotationInstanceStrategy implements AnnotationStrategy { + + final Annotation annotation; + + AnnotationInstanceStrategy(Annotation annotation) { + this.annotation = checkNotNull(annotation, "annotation"); + } + + public boolean hasAttributes() { + return true; + } + + public AnnotationStrategy withoutAttributes() { + return new AnnotationTypeStrategy(getAnnotationType(), annotation); + } + + public Annotation getAnnotation() { + return annotation; + } + + public Class getAnnotationType() { + return annotation.annotationType(); + } + + @Override public boolean equals(Object o) { + if (!(o instanceof AnnotationInstanceStrategy)) { + return false; + } + + AnnotationInstanceStrategy other = (AnnotationInstanceStrategy) o; + return annotation.equals(other.annotation); + } + + @Override public int hashCode() { + return annotation.hashCode(); + } + + @Override public String toString() { + return annotation.toString(); + } + } + + static class AnnotationTypeStrategy implements AnnotationStrategy { + + final Class annotationType; + + // Keep the instance around if we have it so the client can request it. + final Annotation annotation; + + AnnotationTypeStrategy(Class annotationType, + Annotation annotation) { + this.annotationType = checkNotNull(annotationType, "annotation type"); + this.annotation = annotation; + } + + public boolean hasAttributes() { + return false; + } + + public AnnotationStrategy withoutAttributes() { + throw new UnsupportedOperationException("Key already has no attributes."); + } + + public Annotation getAnnotation() { + return annotation; + } + + public Class getAnnotationType() { + return annotationType; + } + + @Override public boolean equals(Object o) { + if (!(o instanceof AnnotationTypeStrategy)) { + return false; + } + + AnnotationTypeStrategy other = (AnnotationTypeStrategy) o; + return annotationType.equals(other.annotationType); + } + + @Override public int hashCode() { + return annotationType.hashCode(); + } + + @Override public String toString() { + return "@" + annotationType.getName(); + } + } + + static boolean isBindingAnnotation(Annotation annotation) { + return isBindingAnnotation(annotation.annotationType()); + } + + static boolean isBindingAnnotation( + Class annotationType) { + return annotationType.isAnnotationPresent(BindingAnnotation.class); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/LookupProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/LookupProcessor.java new file mode 100644 index 00000000000..7cded6fd497 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/LookupProcessor.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.spi.MembersInjectorLookup; +import org.elasticsearch.util.guice.inject.spi.ProviderLookup; + +/** + * Handles {@link Binder#getProvider} and {@link Binder#getMembersInjector(TypeLiteral)} commands. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +class LookupProcessor extends AbstractProcessor { + + LookupProcessor(Errors errors) { + super(errors); + } + + @Override public Boolean visit(MembersInjectorLookup lookup) { + try { + MembersInjector membersInjector + = injector.membersInjectorStore.get(lookup.getType(), errors); + lookup.initializeDelegate(membersInjector); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); // TODO: source + } + + return true; + } + + @Override public Boolean visit(ProviderLookup lookup) { + // ensure the provider can be created + try { + Provider provider = injector.getProviderOrThrow(lookup.getKey(), errors); + lookup.initializeDelegate(provider); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); // TODO: source + } + + return true; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Lookups.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Lookups.java new file mode 100644 index 00000000000..085459753c0 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Lookups.java @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * Accessors for providers and members injectors. The returned values will not be functional until + * the injector has been created. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +interface Lookups { + + Provider getProvider(Key key); + + MembersInjector getMembersInjector(TypeLiteral type); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjector.java new file mode 100644 index 00000000000..65d32f73c9d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjector.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * Injects dependencies into the fields and methods on instances of type {@code T}. Ignores the + * presence or absence of an injectable constructor. + * + * @param type to inject members of + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface MembersInjector { + + /** + * Injects dependencies into the fields and methods of {@code instance}. Ignores the presence or + * absence of an injectable constructor. + * + *

Whenever Guice creates an instance, it performs this injection automatically (after first + * performing constructor injection), so if you're able to let Guice create all your objects for + * you, you'll never need to use this method. + * + * @param instance to inject members on. May be {@code null}. + */ + void injectMembers(T instance); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjectorImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjectorImpl.java new file mode 100644 index 00000000000..5650dcaabf6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjectorImpl.java @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.spi.InjectionListener; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +/** + * Injects members of instances of a given type. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class MembersInjectorImpl implements MembersInjector { + private final TypeLiteral typeLiteral; + private final InjectorImpl injector; + private final ImmutableList memberInjectors; + private final ImmutableList> userMembersInjectors; + private final ImmutableList> injectionListeners; + + MembersInjectorImpl(InjectorImpl injector, TypeLiteral typeLiteral, + EncounterImpl encounter, ImmutableList memberInjectors) { + this.injector = injector; + this.typeLiteral = typeLiteral; + this.memberInjectors = memberInjectors; + this.userMembersInjectors = encounter.getMembersInjectors(); + this.injectionListeners = encounter.getInjectionListeners(); + } + + public ImmutableList getMemberInjectors() { + return memberInjectors; + } + + public void injectMembers(T instance) { + Errors errors = new Errors(typeLiteral); + try { + injectAndNotify(instance, errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + + errors.throwProvisionExceptionIfErrorsExist(); + } + + void injectAndNotify(final T instance, final Errors errors) throws ErrorsException { + if (instance == null) { + return; + } + + injector.callInContext(new ContextualCallable() { + public Void call(InternalContext context) throws ErrorsException { + injectMembers(instance, errors, context); + return null; + } + }); + + notifyListeners(instance, errors); + } + + void notifyListeners(T instance, Errors errors) throws ErrorsException { + int numErrorsBefore = errors.size(); + for (InjectionListener injectionListener : injectionListeners) { + try { + injectionListener.afterInjection(instance); + } catch (RuntimeException e) { + errors.errorNotifyingInjectionListener(injectionListener, typeLiteral, e); + } + } + errors.throwIfNewErrors(numErrorsBefore); + } + + void injectMembers(T t, Errors errors, InternalContext context) { + // optimization: use manual for/each to save allocating an iterator here + for (int i = 0, size = memberInjectors.size(); i < size; i++) { + memberInjectors.get(i).inject(errors, context, t); + } + + // optimization: use manual for/each to save allocating an iterator here + for (int i = 0, size = userMembersInjectors.size(); i < size; i++) { + MembersInjector userMembersInjector = userMembersInjectors.get(i); + try { + userMembersInjector.injectMembers(t); + } catch (RuntimeException e) { + errors.errorInUserInjector(userMembersInjector, typeLiteral, e); + } + } + } + + @Override public String toString() { + return "MembersInjector<" + typeLiteral + ">"; + } + + public ImmutableSet getInjectionPoints() { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (SingleMemberInjector memberInjector : memberInjectors) { + builder.add(memberInjector.getInjectionPoint()); + } + return builder.build(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjectorStore.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjectorStore.java new file mode 100644 index 00000000000..6b3b12e5803 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MembersInjectorStore.java @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.FailableCache; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Set; + +/** + * Members injectors by type. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class MembersInjectorStore { + private final InjectorImpl injector; + private final ImmutableList typeListenerBindings; + + private final FailableCache, MembersInjectorImpl> cache + = new FailableCache, MembersInjectorImpl>() { + @Override protected MembersInjectorImpl create(TypeLiteral type, Errors errors) + throws ErrorsException { + return createWithListeners(type, errors); + } + }; + + MembersInjectorStore(InjectorImpl injector, + List typeListenerBindings) { + this.injector = injector; + this.typeListenerBindings = ImmutableList.copyOf(typeListenerBindings); + } + + /** + * Returns true if any type listeners are installed. Other code may take shortcuts when there + * aren't any type listeners. + */ + public boolean hasTypeListeners() { + return !typeListenerBindings.isEmpty(); + } + + /** + * Returns a new complete members injector with injection listeners registered. + */ + @SuppressWarnings("unchecked") // the MembersInjector type always agrees with the passed type + public MembersInjectorImpl get(TypeLiteral key, Errors errors) throws ErrorsException { + return (MembersInjectorImpl) cache.get(key, errors); + } + + /** + * Creates a new members injector and attaches both injection listeners and method aspects. + */ + private MembersInjectorImpl createWithListeners(TypeLiteral type, Errors errors) + throws ErrorsException { + int numErrorsBefore = errors.size(); + + Set injectionPoints; + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(type); + } catch (ConfigurationException e) { + errors.merge(e.getErrorMessages()); + injectionPoints = e.getPartialValue(); + } + ImmutableList injectors = getInjectors(injectionPoints, errors); + errors.throwIfNewErrors(numErrorsBefore); + + EncounterImpl encounter = new EncounterImpl(errors, injector.lookups); + for (TypeListenerBinding typeListener : typeListenerBindings) { + if (typeListener.getTypeMatcher().matches(type)) { + try { + typeListener.getListener().hear(type, encounter); + } catch (RuntimeException e) { + errors.errorNotifyingTypeListener(typeListener, type, e); + } + } + } + encounter.invalidate(); + errors.throwIfNewErrors(numErrorsBefore); + + return new MembersInjectorImpl(injector, type, encounter, injectors); + } + + /** + * Returns the injectors for the specified injection points. + */ + ImmutableList getInjectors( + Set injectionPoints, Errors errors) { + List injectors = Lists.newArrayList(); + for (InjectionPoint injectionPoint : injectionPoints) { + try { + Errors errorsForMember = injectionPoint.isOptional() + ? new Errors(injectionPoint) + : errors.withSource(injectionPoint); + SingleMemberInjector injector = injectionPoint.getMember() instanceof Field + ? new SingleFieldInjector(this.injector, injectionPoint, errorsForMember) + : new SingleMethodInjector(this.injector, injectionPoint, errorsForMember); + injectors.add(injector); + } catch (ErrorsException ignoredForNow) { + // ignored for now + } + } + return ImmutableList.copyOf(injectors); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MessageProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MessageProcessor.java new file mode 100644 index 00000000000..33369861838 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/MessageProcessor.java @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.Message; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Handles {@link Binder#addError} commands. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +class MessageProcessor extends AbstractProcessor { + + private static final Logger logger = Logger.getLogger(Guice.class.getName()); + + MessageProcessor(Errors errors) { + super(errors); + } + + @Override public Boolean visit(Message message) { + if (message.getCause() != null) { + String rootMessage = getRootMessage(message.getCause()); + logger.log(Level.INFO, + "An exception was caught and reported. Message: " + rootMessage, + message.getCause()); + } + + errors.addMessage(message); + return true; + } + + public static String getRootMessage(Throwable t) { + Throwable cause = t.getCause(); + return cause == null ? t.toString() : getRootMessage(cause); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Module.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Module.java new file mode 100644 index 00000000000..228881805fd --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Module.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * A module contributes configuration information, typically interface + * bindings, which will be used to create an {@link Injector}. A Guice-based + * application is ultimately composed of little more than a set of + * {@code Module}s and some bootstrapping code. + * + *

Your Module classes can use a more streamlined syntax by extending + * {@link AbstractModule} rather than implementing this interface directly. + * + *

In addition to the bindings configured via {@link #configure}, bindings + * will be created for all methods annotated with {@literal @}{@link Provides}. + * Use scope and binding annotations on these methods to configure the + * bindings. + */ +public interface Module { + + /** + * Contributes bindings and other configurations for this module to {@code binder}. + * + *

Do not invoke this method directly to install submodules. Instead use + * {@link Binder#install(Module)}, which ensures that {@link Provides provider methods} are + * discovered. + */ + void configure(Binder binder); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/OutOfScopeException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/OutOfScopeException.java new file mode 100644 index 00000000000..6994f98c17d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/OutOfScopeException.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * Thrown from {@link Provider#get} when an attempt is made to access a scoped + * object while the scope in question is not currently active. + * + * @author kevinb@google.com (Kevin Bourrillion) + * @since 2.0 + */ +public final class OutOfScopeException extends RuntimeException { + + public OutOfScopeException(String message) { + super(message); + } + + public OutOfScopeException(String message, Throwable cause) { + super(message, cause); + } + + public OutOfScopeException(Throwable cause) { + super(cause); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateBinder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateBinder.java new file mode 100644 index 00000000000..9b51c9ea17c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateBinder.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.binder.AnnotatedElementBuilder; + +/** + * Returns a binder whose configuration information is hidden from its environment by default. See + * {@link org.elasticsearch.util.guice.inject.PrivateModule PrivateModule} for details. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface PrivateBinder extends Binder { + + /** Makes the binding for {@code key} available to the enclosing environment */ + void expose(Key key); + + /** + * Makes a binding for {@code type} available to the enclosing environment. Use {@link + * org.elasticsearch.util.guice.inject.binder.AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + AnnotatedElementBuilder expose(Class type); + + /** + * Makes a binding for {@code type} available to the enclosing environment. Use {@link + * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + AnnotatedElementBuilder expose(TypeLiteral type); + + PrivateBinder withSource(Object source); + + PrivateBinder skipSources(Class... classesToSkip); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateElementProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateElementProcessor.java new file mode 100644 index 00000000000..3d50b41fc0e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateElementProcessor.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; + +/** + * Handles {@link Binder#newPrivateBinder()} elements. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class PrivateElementProcessor extends AbstractProcessor { + + private final Stage stage; + private final List injectorShellBuilders = Lists.newArrayList(); + + PrivateElementProcessor(Errors errors, Stage stage) { + super(errors); + this.stage = stage; + } + + @Override public Boolean visit(PrivateElements privateElements) { + InjectorShell.Builder builder = new InjectorShell.Builder() + .parent(injector) + .stage(stage) + .privateElements(privateElements); + injectorShellBuilders.add(builder); + return true; + } + + public List getInjectorShellBuilders() { + return injectorShellBuilders; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateModule.java new file mode 100644 index 00000000000..420ccba75b1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/PrivateModule.java @@ -0,0 +1,281 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.binder.AnnotatedBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.AnnotatedConstantBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.AnnotatedElementBuilder; +import org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.spi.TypeConverter; +import org.elasticsearch.util.guice.inject.spi.TypeListener; +import java.lang.annotation.Annotation; + +/** + * A module whose configuration information is hidden from its environment by default. Only bindings + * that are explicitly exposed will be available to other modules and to the users of the injector. + * This module may expose the bindings it creates and the bindings of the modules it installs. + * + *

A private module can be nested within a regular module or within another private module using + * {@link Binder#install install()}. Its bindings live in a new environment that inherits bindings, + * type converters, scopes, and interceptors from the surrounding ("parent") environment. When you + * nest multiple private modules, the result is a tree of environments where the injector's + * environment is the root. + * + *

Guice EDSL bindings can be exposed with {@link #expose(Class) expose()}. {@literal @}{@link + * org.elasticsearch.util.guice.inject.Provides Provides} bindings can be exposed with the {@literal @}{@link + * Exposed} annotation: + * + *

+ * public class FooBarBazModule extends PrivateModule {
+ *   protected void configure() {
+ *     bind(Foo.class).to(RealFoo.class);
+ *     expose(Foo.class);
+ *
+ *     install(new TransactionalBarModule());
+ *     expose(Bar.class).annotatedWith(Transactional.class);
+ *
+ *     bind(SomeImplementationDetail.class);
+ *     install(new MoreImplementationDetailsModule());
+ *   }
+ *
+ *   {@literal @}Provides {@literal @}Exposed
+ *   public Baz provideBaz() {
+ *     return new SuperBaz();
+ *   }
+ * }
+ * 
+ * + *

Private modules are implemented using {@link Injector#createChildInjector(Module[]) parent + * injectors}. When it can satisfy their dependencies, just-in-time bindings will be created in the + * root environment. Such bindings are shared among all environments in the tree. + * + *

The scope of a binding is constrained to its environment. A singleton bound in a private + * module will be unique to its environment. But a binding for the same type in a different private + * module will yield a different instance. + * + *

A shared binding that injects the {@code Injector} gets the root injector, which only has + * access to bindings in the root environment. An explicit binding that injects the {@code Injector} + * gets access to all bindings in the child environment. + * + *

To promote a just-in-time binding to an explicit binding, bind it: + *

+ *   bind(FooImpl.class);
+ * 
+ * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public abstract class PrivateModule implements Module { + + /** Like abstract module, the binder of the current private module */ + private PrivateBinder binder; + + public final synchronized void configure(Binder binder) { + checkState(this.binder == null, "Re-entry is not allowed."); + + // Guice treats PrivateModules specially and passes in a PrivateBinder automatically. + this.binder = (PrivateBinder) binder.skipSources(PrivateModule.class); + try { + configure(); + } finally { + this.binder = null; + } + } + + /** + * Creates bindings and other configurations private to this module. Use {@link #expose(Class) + * expose()} to make the bindings in this module available externally. + */ + protected abstract void configure(); + + /** Makes the binding for {@code key} available to other modules and the injector. */ + protected final void expose(Key key) { + binder.expose(key); + } + + /** + * Makes a binding for {@code type} available to other modules and the injector. Use {@link + * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + protected final AnnotatedElementBuilder expose(Class type) { + return binder.expose(type); + } + + /** + * Makes a binding for {@code type} available to other modules and the injector. Use {@link + * AnnotatedElementBuilder#annotatedWith(Class) annotatedWith()} to expose {@code type} with a + * binding annotation. + */ + protected final AnnotatedElementBuilder expose(TypeLiteral type) { + return binder.expose(type); + } + + // everything below is copied from AbstractModule + + /** + * Returns the current binder. + */ + protected final PrivateBinder binder() { + return binder; + } + + /** + * @see Binder#bindScope(Class, Scope) + */ + protected final void bindScope(Class scopeAnnotation, Scope scope) { + binder.bindScope(scopeAnnotation, scope); + } + + /** + * @see Binder#bind(Key) + */ + protected final LinkedBindingBuilder bind(Key key) { + return binder.bind(key); + } + + /** + * @see Binder#bind(TypeLiteral) + */ + protected final AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) { + return binder.bind(typeLiteral); + } + + /** + * @see Binder#bind(Class) + */ + protected final AnnotatedBindingBuilder bind(Class clazz) { + return binder.bind(clazz); + } + + /** + * @see Binder#bindConstant() + */ + protected final AnnotatedConstantBindingBuilder bindConstant() { + return binder.bindConstant(); + } + + /** + * @see Binder#install(Module) + */ + protected final void install(Module module) { + binder.install(module); + } + + /** + * @see Binder#addError(String, Object[]) + */ + protected final void addError(String message, Object... arguments) { + binder.addError(message, arguments); + } + + /** + * @see Binder#addError(Throwable) + */ + protected final void addError(Throwable t) { + binder.addError(t); + } + + /** + * @see Binder#addError(Message) + */ + protected final void addError(Message message) { + binder.addError(message); + } + + /** + * @see Binder#requestInjection(Object) + */ + protected final void requestInjection(Object instance) { + binder.requestInjection(instance); + } + + /** + * @see Binder#requestStaticInjection(Class[]) + */ + protected final void requestStaticInjection(Class... types) { + binder.requestStaticInjection(types); + } + + /** + * Instructs Guice to require a binding to the given key. + */ + protected final void requireBinding(Key key) { + binder.getProvider(key); + } + + /** + * Instructs Guice to require a binding to the given type. + */ + protected final void requireBinding(Class type) { + binder.getProvider(type); + } + + /** + * @see Binder#getProvider(Key) + */ + protected final Provider getProvider(Key key) { + return binder.getProvider(key); + } + + /** + * @see Binder#getProvider(Class) + */ + protected final Provider getProvider(Class type) { + return binder.getProvider(type); + } + + /** + * @see Binder#convertToTypes(org.elasticsearch.util.guice.inject.matcher.Matcher, org.elasticsearch.util.guice.inject.spi.TypeConverter) + */ + protected final void convertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + binder.convertToTypes(typeMatcher, converter); + } + + /** + * @see Binder#currentStage() + */ + protected final Stage currentStage() { + return binder.currentStage(); + } + + /** + * @see Binder#getMembersInjector(Class) + */ + protected MembersInjector getMembersInjector(Class type) { + return binder.getMembersInjector(type); + } + + /** + * @see Binder#getMembersInjector(TypeLiteral) + */ + protected MembersInjector getMembersInjector(TypeLiteral type) { + return binder.getMembersInjector(type); + } + + /** + * @see Binder#bindListener(org.elasticsearch.util.guice.inject.matcher.Matcher, org.elasticsearch.util.guice.inject.spi.TypeListener) + */ + protected void bindListener(Matcher> typeMatcher, + TypeListener listener) { + binder.bindListener(typeMatcher, listener); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProvidedBy.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProvidedBy.java new file mode 100644 index 00000000000..a07944bcb14 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProvidedBy.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * A pointer to the default provider type for a type. + * + * @author crazybob@google.com (Bob Lee) + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface ProvidedBy { + + /** + * The implementation type. + */ + Class> value(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Provider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Provider.java new file mode 100644 index 00000000000..26f943f309c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Provider.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * An object capable of providing instances of type {@code T}. Providers are used in numerous ways + * by Guice: + * + *
    + *
  • When the default means for obtaining instances (an injectable or parameterless constructor) + * is insufficient for a particular binding, the module can specify a custom {@code Provider} + * instead, to control exactly how Guice creates or obtains instances for the binding. + * + *
  • An implementation class may always choose to have a {@code Provider} instance injected, + * rather than having a {@code T} injected directly. This may give you access to multiple + * instances, instances you wish to safely mutate and discard, instances which are out of scope + * (e.g. using a {@code @RequestScoped} object from within a {@code @SessionScoped} object), or + * instances that will be initialized lazily. + * + *
  • A custom {@link Scope} is implemented as a decorator of {@code Provider}, which decides + * when to delegate to the backing provider and when to provide the instance some other way. + * + *
  • The {@link Injector} offers access to the {@code Provider} it uses to fulfill requests + * for a given key, via the {@link Injector#getProvider} methods. + *
+ * + * @param the type of object this provides + * + * @author crazybob@google.com (Bob Lee) + */ +public interface Provider { + + /** + * Provides an instance of {@code T}. Must never return {@code null}. + * + * @throws OutOfScopeException when an attempt is made to access a scoped object while the scope + * in question is not currently active + * @throws ProvisionException if an instance cannot be provided. Such exceptions include messages + * and throwables to describe why provision failed. + */ + T get(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProviderToInternalFactoryAdapter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProviderToInternalFactoryAdapter.java new file mode 100644 index 00000000000..e239e1f030f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProviderToInternalFactoryAdapter.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * @author crazybob@google.com (Bob Lee) + */ +class ProviderToInternalFactoryAdapter implements Provider { + + private final InjectorImpl injector; + private final InternalFactory internalFactory; + + public ProviderToInternalFactoryAdapter(InjectorImpl injector, + InternalFactory internalFactory) { + this.injector = injector; + this.internalFactory = internalFactory; + } + + public T get() { + final Errors errors = new Errors(); + try { + T t = injector.callInContext(new ContextualCallable() { + public T call(InternalContext context) throws ErrorsException { + Dependency dependency = context.getDependency(); + return internalFactory.get(errors, context, dependency); + } + }); + errors.throwIfNewErrors(0); + return t; + } catch (ErrorsException e) { + throw new ProvisionException(errors.merge(e.getErrors()).getMessages()); + } + } + + @Override public String toString() { + return internalFactory.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Provides.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Provides.java new file mode 100644 index 00000000000..1033d58420b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Provides.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.METHOD; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates methods of a {@link Module} to create a provider method binding. The method's return + * type is bound to it's returned value. Guice will pass dependencies to the method as parameters. + * + * @author crazybob@google.com (Bob Lee) + * @since 2.0 + */ +@Documented @Target(METHOD) @Retention(RUNTIME) +public @interface Provides {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProvisionException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProvisionException.java new file mode 100644 index 00000000000..a6a1ccf3cf5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ProvisionException.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Collection; + +/** + * Indicates that there was a runtime failure while providing an instance. + * + * @author kevinb@google.com (Kevin Bourrillion) + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class ProvisionException extends RuntimeException { + + private final ImmutableSet messages; + + /** Creates a ConfigurationException containing {@code messages}. */ + public ProvisionException(Iterable messages) { + this.messages = ImmutableSet.copyOf(messages); + checkArgument(!this.messages.isEmpty()); + initCause(Errors.getOnlyCause(this.messages)); + } + + public ProvisionException(String message, Throwable cause) { + super(cause); + this.messages = ImmutableSet.of(new Message(ImmutableList.of(), message, cause)); + } + + public ProvisionException(String message) { + this.messages = ImmutableSet.of(new Message(message)); + } + + /** Returns messages for the errors that caused this exception. */ + public Collection getErrorMessages() { + return messages; + } + + @Override public String getMessage() { + return Errors.format("Guice provision errors", messages); + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Reflection.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Reflection.java new file mode 100644 index 00000000000..96c51243361 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Reflection.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject; + +import java.lang.reflect.Constructor; + +/** + * Abstraction for Java's reflection APIs. This interface exists to provide a single place where + * runtime reflection can be substituted for another mechanism such as CGLib or compile-time code + * generation. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class Reflection { + + /** + * A placeholder. This enables us to continue processing and gather more + * errors but blows up if you actually try to use it. + */ + static class InvalidConstructor { + InvalidConstructor() { + throw new AssertionError(); + } + } + + @SuppressWarnings("unchecked") + static Constructor invalidConstructor() { + try { + return (Constructor) InvalidConstructor.class.getDeclaredConstructor(); + } + catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Scope.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Scope.java new file mode 100644 index 00000000000..1662edd5590 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Scope.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * A scope is a level of visibility that instances provided by Guice may have. + * By default, an instance created by the {@link Injector} has no scope, + * meaning it has no state from the framework's perspective -- the + * {@code Injector} creates it, injects it once into the class that required it, + * and then immediately forgets it. Associating a scope with a particular + * binding allows the created instance to be "remembered" and possibly used + * again for other injections. + * + *

An example of a scope is {@link Scopes#SINGLETON}. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface Scope { + + /** + * Scopes a provider. The returned provider returns objects from this scope. + * If an object does not exist in this scope, the provider can use the given + * unscoped provider to retrieve one. + * + *

Scope implementations are strongly encouraged to override + * {@link Object#toString} in the returned provider and include the backing + * provider's {@code toString()} output. + * + * @param key binding key + * @param unscoped locates an instance when one doesn't already exist in this + * scope. + * @return a new provider which only delegates to the given unscoped provider + * when an instance of the requested object doesn't already exist in this + * scope + */ + public Provider scope(Key key, Provider unscoped); + + /** + * A short but useful description of this scope. For comparison, the standard + * scopes that ship with guice use the descriptions + * {@code "Scopes.SINGLETON"}, {@code "ServletScopes.SESSION"} and + * {@code "ServletScopes.REQUEST"}. + */ + String toString(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ScopeAnnotation.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ScopeAnnotation.java new file mode 100644 index 00000000000..2f284440845 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ScopeAnnotation.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates annotations which are used for scoping. Only one such annotation + * may apply to a single implementation class. You must also annotate scope + * annotations with {@code @Retention(RUNTIME)}. For example: + * + *

+ *   {@code @}Retention(RUNTIME)
+ *   {@code @}Target(TYPE)
+ *   {@code @}ScopeAnnotation
+ *   public {@code @}interface SessionScoped {}
+ * 
+ * + * @author crazybob@google.com (Bob Lee) + */ +@Target(ANNOTATION_TYPE) +@Retention(RUNTIME) +public @interface ScopeAnnotation {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ScopeBindingProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ScopeBindingProcessor.java new file mode 100644 index 00000000000..43483eeac25 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/ScopeBindingProcessor.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Annotations; +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.ScopeBinding; +import java.lang.annotation.Annotation; + +/** + * Handles {@link Binder#bindScope} commands. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +class ScopeBindingProcessor extends AbstractProcessor { + + ScopeBindingProcessor(Errors errors) { + super(errors); + } + + @Override public Boolean visit(ScopeBinding command) { + Scope scope = command.getScope(); + Class annotationType = command.getAnnotationType(); + + if (!Annotations.isScopeAnnotation(annotationType)) { + errors.withSource(annotationType).missingScopeAnnotation(); + // Go ahead and bind anyway so we don't get collateral errors. + } + + if (!Annotations.isRetainedAtRuntime(annotationType)) { + errors.withSource(annotationType) + .missingRuntimeRetention(command.getSource()); + // Go ahead and bind anyway so we don't get collateral errors. + } + + Scope existing = injector.state.getScope(checkNotNull(annotationType, "annotation type")); + if (existing != null) { + errors.duplicateScopes(existing, annotationType, scope); + } else { + injector.state.putAnnotation(annotationType, checkNotNull(scope, "scope")); + } + + return true; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Scopes.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Scopes.java new file mode 100644 index 00000000000..9dbceb9f528 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Scopes.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.internal.Scoping; +import java.lang.annotation.Annotation; + +/** + * Built-in scope implementations. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Scopes { + + private Scopes() {} + + /** + * One instance per {@link Injector}. Also see {@code @}{@link Singleton}. + */ + public static final Scope SINGLETON = new Scope() { + public Provider scope(Key key, final Provider creator) { + return new Provider() { + + private volatile T instance; + + // DCL on a volatile is safe as of Java 5, which we obviously require. + @SuppressWarnings("DoubleCheckedLocking") + public T get() { + if (instance == null) { + /* + * Use a pretty coarse lock. We don't want to run into deadlocks + * when two threads try to load circularly-dependent objects. + * Maybe one of these days we will identify independent graphs of + * objects and offer to load them in parallel. + */ + synchronized (InjectorImpl.class) { + if (instance == null) { + instance = creator.get(); + } + } + } + return instance; + } + + public String toString() { + return String.format("%s[%s]", creator, SINGLETON); + } + }; + } + + @Override public String toString() { + return "Scopes.SINGLETON"; + } + }; + + /** + * No scope; the same as not applying any scope at all. Each time the + * Injector obtains an instance of an object with "no scope", it injects this + * instance then immediately forgets it. When the next request for the same + * binding arrives it will need to obtain the instance over again. + * + *

This exists only in case a class has been annotated with a scope + * annotation such as {@link Singleton @Singleton}, and you need to override + * this to "no scope" in your binding. + * + * @since 2.0 + */ + public static final Scope NO_SCOPE = new Scope() { + public Provider scope(Key key, Provider unscoped) { + return unscoped; + } + @Override public String toString() { + return "Scopes.NO_SCOPE"; + } + }; + + /** Scopes an internal factory. */ + static InternalFactory scope(Key key, InjectorImpl injector, + InternalFactory creator, Scoping scoping) { + + if (scoping.isNoScope()) { + return creator; + } + + Scope scope = scoping.getScopeInstance(); + + Provider scoped + = scope.scope(key, new ProviderToInternalFactoryAdapter(injector, creator)); + return new InternalFactoryToProviderAdapter( + Initializables.>of(scoped)); + } + + /** + * Replaces annotation scopes with instance scopes using the Injector's annotation-to-instance + * map. If the scope annotation has no corresponding instance, an error will be added and unscoped + * will be retuned. + */ + static Scoping makeInjectable(Scoping scoping, InjectorImpl injector, Errors errors) { + Class scopeAnnotation = scoping.getScopeAnnotation(); + if (scopeAnnotation == null) { + return scoping; + } + + Scope scope = injector.state.getScope(scopeAnnotation); + if (scope != null) { + return Scoping.forInstance(scope); + } + + errors.scopeNotFound(scopeAnnotation); + return Scoping.UNSCOPED; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleFieldInjector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleFieldInjector.java new file mode 100644 index 00000000000..de6fb3f111c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleFieldInjector.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import java.lang.reflect.Field; + +/** + * Sets an injectable field. + */ +class SingleFieldInjector implements SingleMemberInjector { + final Field field; + final InjectionPoint injectionPoint; + final Dependency dependency; + final InternalFactory factory; + + public SingleFieldInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors) + throws ErrorsException { + this.injectionPoint = injectionPoint; + this.field = (Field) injectionPoint.getMember(); + this.dependency = injectionPoint.getDependencies().get(0); + + // Ewwwww... + field.setAccessible(true); + factory = injector.getInternalFactory(dependency.getKey(), errors); + } + + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + public void inject(Errors errors, InternalContext context, Object o) { + errors = errors.withSource(dependency); + + context.setDependency(dependency); + try { + Object value = factory.get(errors, context, dependency); + field.set(o, value); + } catch (ErrorsException e) { + errors.withSource(injectionPoint).merge(e.getErrors()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); // a security manager is blocking us, we're hosed + } finally { + context.setDependency(null); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleMemberInjector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleMemberInjector.java new file mode 100644 index 00000000000..3edd2447b10 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleMemberInjector.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; + +/** + * Injects a field or method of a given object. + */ +interface SingleMemberInjector { + void inject(Errors errors, InternalContext context, Object o); + InjectionPoint getInjectionPoint(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleMethodInjector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleMethodInjector.java new file mode 100644 index 00000000000..a0a2590c1a4 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleMethodInjector.java @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.InjectorImpl.MethodInvoker; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * Invokes an injectable method. + */ +class SingleMethodInjector implements SingleMemberInjector { + final MethodInvoker methodInvoker; + final SingleParameterInjector[] parameterInjectors; + final InjectionPoint injectionPoint; + + public SingleMethodInjector(InjectorImpl injector, InjectionPoint injectionPoint, Errors errors) + throws ErrorsException { + this.injectionPoint = injectionPoint; + final Method method = (Method) injectionPoint.getMember(); + methodInvoker = createMethodInvoker(method); + parameterInjectors = injector.getParametersInjectors(injectionPoint.getDependencies(), errors); + } + + private MethodInvoker createMethodInvoker(final Method method) { + + // We can't use FastMethod if the method is private. + int modifiers = method.getModifiers(); + if (!Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers)) { + } + + if (!Modifier.isPublic(modifiers)) { + method.setAccessible(true); + } + + return new MethodInvoker() { + public Object invoke(Object target, Object... parameters) + throws IllegalAccessException, InvocationTargetException { + return method.invoke(target, parameters); + } + }; + } + + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + public void inject(Errors errors, InternalContext context, Object o) { + Object[] parameters; + try { + parameters = SingleParameterInjector.getAll(errors, context, parameterInjectors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + return; + } + + try { + methodInvoker.invoke(o, parameters); + } catch (IllegalAccessException e) { + throw new AssertionError(e); // a security manager is blocking us, we're hosed + } catch (InvocationTargetException userException) { + Throwable cause = userException.getCause() != null + ? userException.getCause() + : userException; + errors.withSource(injectionPoint).errorInjectingMethod(cause); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleParameterInjector.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleParameterInjector.java new file mode 100644 index 00000000000..0328e1bec2b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/SingleParameterInjector.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.InternalContext; +import org.elasticsearch.util.guice.inject.internal.InternalFactory; +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * Resolves a single parameter, to be used in a constructor or method invocation. + */ +class SingleParameterInjector { + private static final Object[] NO_ARGUMENTS = {}; + + private final Dependency dependency; + private final InternalFactory factory; + + SingleParameterInjector(Dependency dependency, InternalFactory factory) { + this.dependency = dependency; + this.factory = factory; + } + + private T inject(Errors errors, InternalContext context) throws ErrorsException { + context.setDependency(dependency); + try { + return factory.get(errors.withSource(dependency), context, dependency); + } finally { + context.setDependency(null); + } + } + + /** + * Returns an array of parameter values. + */ + static Object[] getAll(Errors errors, InternalContext context, + SingleParameterInjector[] parameterInjectors) throws ErrorsException { + if (parameterInjectors == null) { + return NO_ARGUMENTS; + } + + int numErrorsBefore = errors.size(); + + int size = parameterInjectors.length; + Object[] parameters = new Object[size]; + + // optimization: use manual for/each to save allocating an iterator here + for (int i = 0; i < size; i++) { + SingleParameterInjector parameterInjector = parameterInjectors[i]; + try { + parameters[i] = parameterInjector.inject(errors, context); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + errors.throwIfNewErrors(numErrorsBefore); + return parameters; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Singleton.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Singleton.java new file mode 100644 index 00000000000..57851a95eaf --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Singleton.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Apply this to implementation classes when you want only one instance + * (per {@link Injector}) to be reused for all injections for that binding. + * + * @author crazybob@google.com (Bob Lee) + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RUNTIME) +@ScopeAnnotation +public @interface Singleton {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Stage.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Stage.java new file mode 100644 index 00000000000..0ed4566c61c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/Stage.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +/** + * The stage we're running in. + * + * @author crazybob@google.com (Bob Lee) + */ +public enum Stage { + + /** + * We're running in a tool (an IDE plugin for example). We need binding meta data but not a + * functioning Injector. Do not inject members of instances. Do not load eager singletons. Do as + * little as possible so our tools run nice and snappy. Injectors created in this stage cannot + * be used to satisfy injections. + */ + TOOL, + + /** + * We want fast startup times at the expense of runtime performance and some up front error + * checking. + */ + DEVELOPMENT, + + /** + * We want to catch errors as early as possible and take performance hits up front. + */ + PRODUCTION +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/State.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/State.java new file mode 100644 index 00000000000..e4e7103b965 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/State.java @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.BindingImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.MatcherAndConverter; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; + +/** + * The inheritable data within an injector. This class is intended to allow parent and local + * injector data to be accessed as a unit. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +interface State { + + static final State NONE = new State() { + public State parent() { + throw new UnsupportedOperationException(); + } + + public BindingImpl getExplicitBinding(Key key) { + return null; + } + + public Map, Binding> getExplicitBindingsThisLevel() { + throw new UnsupportedOperationException(); + } + + public void putBinding(Key key, BindingImpl binding) { + throw new UnsupportedOperationException(); + } + + public Scope getScope(Class scopingAnnotation) { + return null; + } + + public void putAnnotation(Class annotationType, Scope scope) { + throw new UnsupportedOperationException(); + } + + public void addConverter(MatcherAndConverter matcherAndConverter) { + throw new UnsupportedOperationException(); + } + + public MatcherAndConverter getConverter(String stringValue, TypeLiteral type, Errors errors, + Object source) { + throw new UnsupportedOperationException(); + } + + public Iterable getConvertersThisLevel() { + return ImmutableSet.of(); + } + + public void addTypeListener(TypeListenerBinding typeListenerBinding) { + throw new UnsupportedOperationException(); + } + + public List getTypeListenerBindings() { + return ImmutableList.of(); + } + + public void blacklist(Key key) { + } + + public boolean isBlacklisted(Key key) { + return true; + } + + public Object lock() { + throw new UnsupportedOperationException(); + } + }; + + State parent(); + + /** Gets a binding which was specified explicitly in a module, or null. */ + BindingImpl getExplicitBinding(Key key); + + /** Returns the explicit bindings at this level only. */ + Map, Binding> getExplicitBindingsThisLevel(); + + void putBinding(Key key, BindingImpl binding); + + /** Returns the matching scope, or null. */ + Scope getScope(Class scopingAnnotation); + + void putAnnotation(Class annotationType, Scope scope); + + void addConverter(MatcherAndConverter matcherAndConverter); + + /** Returns the matching converter for {@code type}, or null if none match. */ + MatcherAndConverter getConverter( + String stringValue, TypeLiteral type, Errors errors, Object source); + + /** Returns all converters at this level only. */ + Iterable getConvertersThisLevel(); + + void addTypeListener(TypeListenerBinding typeListenerBinding); + + List getTypeListenerBindings(); + + /** + * Forbids the corresponding injector from creating a binding to {@code key}. Child injectors + * blacklist their bound keys on their parent injectors to prevent just-in-time bindings on the + * parent injector that would conflict. + */ + void blacklist(Key key); + + /** + * Returns true if {@code key} is forbidden from being bound in this injector. This indicates that + * one of this injector's descendent's has bound the key. + */ + boolean isBlacklisted(Key key); + + /** + * Returns the shared lock for all injector data. This is a low-granularity, high-contention lock + * to be used when reading mutable data (ie. just-in-time bindings, and binding blacklists). + */ + Object lock(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeConverterBindingProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeConverterBindingProcessor.java new file mode 100644 index 00000000000..5684139d1fa --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeConverterBindingProcessor.java @@ -0,0 +1,174 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.MatcherAndConverter; +import org.elasticsearch.util.guice.inject.internal.SourceProvider; +import org.elasticsearch.util.guice.inject.internal.Strings; +import org.elasticsearch.util.guice.inject.matcher.AbstractMatcher; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.guice.inject.matcher.Matchers; +import org.elasticsearch.util.guice.inject.spi.TypeConverter; +import org.elasticsearch.util.guice.inject.spi.TypeConverterBinding; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * Handles {@link Binder#convertToTypes} commands. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +class TypeConverterBindingProcessor extends AbstractProcessor { + + TypeConverterBindingProcessor(Errors errors) { + super(errors); + } + + /** Installs default converters for primitives, enums, and class literals. */ + public void prepareBuiltInConverters(InjectorImpl injector) { + this.injector = injector; + try { + // Configure type converters. + convertToPrimitiveType(int.class, Integer.class); + convertToPrimitiveType(long.class, Long.class); + convertToPrimitiveType(boolean.class, Boolean.class); + convertToPrimitiveType(byte.class, Byte.class); + convertToPrimitiveType(short.class, Short.class); + convertToPrimitiveType(float.class, Float.class); + convertToPrimitiveType(double.class, Double.class); + + convertToClass(Character.class, new TypeConverter() { + public Object convert(String value, TypeLiteral toType) { + value = value.trim(); + if (value.length() != 1) { + throw new RuntimeException("Length != 1."); + } + return value.charAt(0); + } + + @Override public String toString() { + return "TypeConverter"; + } + }); + + convertToClasses(Matchers.subclassesOf(Enum.class), new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convert(String value, TypeLiteral toType) { + return Enum.valueOf((Class) toType.getRawType(), value); + } + + @Override public String toString() { + return "TypeConverter>"; + } + }); + + internalConvertToTypes( + new AbstractMatcher>() { + public boolean matches(TypeLiteral typeLiteral) { + return typeLiteral.getRawType() == Class.class; + } + + @Override public String toString() { + return "Class"; + } + }, + new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convert(String value, TypeLiteral toType) { + try { + return Class.forName(value); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e.getMessage()); + } + } + + @Override public String toString() { + return "TypeConverter>"; + } + } + ); + } finally { + this.injector = null; + } + } + + private void convertToPrimitiveType(Class primitiveType, final Class wrapperType) { + try { + final Method parser = wrapperType.getMethod( + "parse" + Strings.capitalize(primitiveType.getName()), String.class); + + TypeConverter typeConverter = new TypeConverter() { + @SuppressWarnings("unchecked") + public Object convert(String value, TypeLiteral toType) { + try { + return parser.invoke(null, value); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getTargetException().getMessage()); + } + } + + @Override public String toString() { + return "TypeConverter<" + wrapperType.getSimpleName() + ">"; + } + }; + + convertToClass(wrapperType, typeConverter); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + private void convertToClass(Class type, TypeConverter converter) { + convertToClasses(Matchers.identicalTo(type), converter); + } + + private void convertToClasses(final Matcher> typeMatcher, + TypeConverter converter) { + internalConvertToTypes(new AbstractMatcher>() { + public boolean matches(TypeLiteral typeLiteral) { + Type type = typeLiteral.getType(); + if (!(type instanceof Class)) { + return false; + } + Class clazz = (Class) type; + return typeMatcher.matches(clazz); + } + + @Override public String toString() { + return typeMatcher.toString(); + } + }, converter); + } + + private void internalConvertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + injector.state.addConverter( + new MatcherAndConverter(typeMatcher, converter, SourceProvider.UNKNOWN_SOURCE)); + } + + @Override public Boolean visit(TypeConverterBinding command) { + injector.state.addConverter(new MatcherAndConverter( + command.getTypeMatcher(), command.getTypeConverter(), command.getSource())); + return true; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeListenerBindingProcessor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeListenerBindingProcessor.java new file mode 100644 index 00000000000..633d208baca --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeListenerBindingProcessor.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; + +/** + * Handles {@link Binder#bindListener} commands. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +class TypeListenerBindingProcessor extends AbstractProcessor { + + TypeListenerBindingProcessor(Errors errors) { + super(errors); + } + + @Override public Boolean visit(TypeListenerBinding binding) { + injector.state.addTypeListener(binding); + return true; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeLiteral.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeLiteral.java new file mode 100644 index 00000000000..ce47a8848ac --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/TypeLiteral.java @@ -0,0 +1,346 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.guice.inject.internal.MoreTypes; +import static org.elasticsearch.util.guice.inject.internal.MoreTypes.canonicalize; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.util.Types; +import org.elasticsearch.util.gcommon.collect.ImmutableList; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.List; + +/** + * Represents a generic type {@code T}. Java doesn't yet provide a way to + * represent generic types, so this class does. Forces clients to create a + * subclass of this class which enables retrieval the type information even at + * runtime. + * + *

For example, to create a type literal for {@code List}, you can + * create an empty anonymous inner class: + * + *

+ * {@code TypeLiteral> list = new TypeLiteral>() {};} + * + *

This syntax cannot be used to create type literals that have wildcard + * parameters, such as {@code Class} or {@code List}. + * Such type literals must be constructed programatically, either by {@link + * Method#getGenericReturnType extracting types from members} or by using the + * {@link Types} factory class. + * + *

Along with modeling generic types, this class can resolve type parameters. + * For example, to figure out what type {@code keySet()} returns on a {@code + * Map}, use this code:

   {@code
+ *
+ *   TypeLiteral> mapType
+ *       = new TypeLiteral>() {};
+ *   TypeLiteral keySetType
+ *       = mapType.getReturnType(Map.class.getMethod("keySet"));
+ *   System.out.println(keySetType); // prints "Set"}
+ * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +public class TypeLiteral { + + final Class rawType; + final Type type; + final int hashCode; + + /** + * Constructs a new type literal. Derives represented class from type + * parameter. + * + *

Clients create an empty anonymous subclass. Doing so embeds the type + * parameter in the anonymous class's type hierarchy so we can reconstitute it + * at runtime despite erasure. + */ + @SuppressWarnings("unchecked") + protected TypeLiteral() { + this.type = getSuperclassTypeParameter(getClass()); + this.rawType = (Class) MoreTypes.getRawType(type); + this.hashCode = MoreTypes.hashCode(type); + } + + /** + * Unsafe. Constructs a type literal manually. + */ + @SuppressWarnings("unchecked") + TypeLiteral(Type type) { + this.type = canonicalize(checkNotNull(type, "type")); + this.rawType = (Class) MoreTypes.getRawType(this.type); + this.hashCode = MoreTypes.hashCode(this.type); + } + + /** + * Returns the type from super class's type parameter in {@link MoreTypes#canonicalize(Type) + * canonical form}. + */ + static Type getSuperclassTypeParameter(Class subclass) { + Type superclass = subclass.getGenericSuperclass(); + if (superclass instanceof Class) { + throw new RuntimeException("Missing type parameter."); + } + ParameterizedType parameterized = (ParameterizedType) superclass; + return canonicalize(parameterized.getActualTypeArguments()[0]); + } + + /** + * Gets type literal from super class's type parameter. + */ + static TypeLiteral fromSuperclassTypeParameter(Class subclass) { + return new TypeLiteral(getSuperclassTypeParameter(subclass)); + } + + /** + * Returns the raw (non-generic) type for this type. + * + * @since 2.0 + */ + public final Class getRawType() { + return rawType; + } + + /** + * Gets underlying {@code Type} instance. + */ + public final Type getType() { + return type; + } + + /** + * Gets the type of this type's provider. + */ + @SuppressWarnings("unchecked") + final TypeLiteral> providerType() { + // This cast is safe and wouldn't generate a warning if Type had a type + // parameter. + return (TypeLiteral>) get(Types.providerOf(getType())); + } + + @Override public final int hashCode() { + return this.hashCode; + } + + @Override public final boolean equals(Object o) { + return o instanceof TypeLiteral + && MoreTypes.equals(type, ((TypeLiteral) o).type); + } + + @Override public final String toString() { + return MoreTypes.toString(type); + } + + /** + * Gets type literal for the given {@code Type} instance. + */ + public static TypeLiteral get(Type type) { + return new TypeLiteral(type); + } + + /** + * Gets type literal for the given {@code Class} instance. + */ + public static TypeLiteral get(Class type) { + return new TypeLiteral(type); + } + + + /** Returns an immutable list of the resolved types. */ + private List> resolveAll(Type[] types) { + TypeLiteral[] result = new TypeLiteral[types.length]; + for (int t = 0; t < types.length; t++) { + result[t] = resolve(types[t]); + } + return ImmutableList.of(result); + } + + /** + * Resolves known type parameters in {@code toResolve} and returns the result. + */ + TypeLiteral resolve(Type toResolve) { + return TypeLiteral.get(resolveType(toResolve)); + } + + Type resolveType(Type toResolve) { + // this implementation is made a little more complicated in an attempt to avoid object-creation + while (true) { + if (toResolve instanceof TypeVariable) { + TypeVariable original = (TypeVariable) toResolve; + toResolve = MoreTypes.resolveTypeVariable(type, rawType, original); + if (toResolve == original) { + return toResolve; + } + + } else if (toResolve instanceof GenericArrayType) { + GenericArrayType original = (GenericArrayType) toResolve; + Type componentType = original.getGenericComponentType(); + Type newComponentType = resolveType(componentType); + return componentType == newComponentType + ? original + : Types.arrayOf(newComponentType); + + } else if (toResolve instanceof ParameterizedType) { + ParameterizedType original = (ParameterizedType) toResolve; + Type ownerType = original.getOwnerType(); + Type newOwnerType = resolveType(ownerType); + boolean changed = newOwnerType != ownerType; + + Type[] args = original.getActualTypeArguments(); + for (int t = 0, length = args.length; t < length; t++) { + Type resolvedTypeArgument = resolveType(args[t]); + if (resolvedTypeArgument != args[t]) { + if (!changed) { + args = args.clone(); + changed = true; + } + args[t] = resolvedTypeArgument; + } + } + + return changed + ? Types.newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) + : original; + + } else if (toResolve instanceof WildcardType) { + WildcardType original = (WildcardType) toResolve; + Type[] originalLowerBound = original.getLowerBounds(); + Type[] originalUpperBound = original.getUpperBounds(); + + if (originalLowerBound.length == 1) { + Type lowerBound = resolveType(originalLowerBound[0]); + if (lowerBound != originalLowerBound[0]) { + return Types.supertypeOf(lowerBound); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolveType(originalUpperBound[0]); + if (upperBound != originalUpperBound[0]) { + return Types.subtypeOf(upperBound); + } + } + return original; + + } else { + return toResolve; + } + } + } + + /** + * Returns the generic form of {@code supertype}. For example, if this is {@code + * ArrayList}, this returns {@code Iterable} given the input {@code + * Iterable.class}. + * + * @param supertype a superclass of, or interface implemented by, this. + * @since 2.0 + */ + public TypeLiteral getSupertype(Class supertype) { + checkArgument(supertype.isAssignableFrom(rawType), + "%s is not a supertype of %s", supertype, this.type); + return resolve(MoreTypes.getGenericSupertype(type, rawType, supertype)); + } + + /** + * Returns the resolved generic type of {@code field}. + * + * @param field a field defined by this or any superclass. + * @since 2.0 + */ + public TypeLiteral getFieldType(Field field) { + checkArgument(field.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", field, type); + return resolve(field.getGenericType()); + } + + /** + * Returns the resolved generic parameter types of {@code methodOrConstructor}. + * + * @param methodOrConstructor a method or constructor defined by this or any supertype. + * @since 2.0 + */ + public List> getParameterTypes(Member methodOrConstructor) { + Type[] genericParameterTypes; + + if (methodOrConstructor instanceof Method) { + Method method = (Method) methodOrConstructor; + checkArgument(method.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", method, type); + genericParameterTypes = method.getGenericParameterTypes(); + + } else if (methodOrConstructor instanceof Constructor) { + Constructor constructor = (Constructor) methodOrConstructor; + checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType), + "%s does not construct a supertype of %s", constructor, type); + genericParameterTypes = constructor.getGenericParameterTypes(); + + } else { + throw new IllegalArgumentException("Not a method or a constructor: " + methodOrConstructor); + } + + return resolveAll(genericParameterTypes); + } + + /** + * Returns the resolved generic exception types thrown by {@code constructor}. + * + * @param methodOrConstructor a method or constructor defined by this or any supertype. + * @since 2.0 + */ + public List> getExceptionTypes(Member methodOrConstructor) { + Type[] genericExceptionTypes; + + if (methodOrConstructor instanceof Method) { + Method method = (Method) methodOrConstructor; + checkArgument(method.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", method, type); + genericExceptionTypes = method.getGenericExceptionTypes(); + + } else if (methodOrConstructor instanceof Constructor) { + Constructor constructor = (Constructor) methodOrConstructor; + checkArgument(constructor.getDeclaringClass().isAssignableFrom(rawType), + "%s does not construct a supertype of %s", constructor, type); + genericExceptionTypes = constructor.getGenericExceptionTypes(); + + } else { + throw new IllegalArgumentException("Not a method or a constructor: " + methodOrConstructor); + } + + return resolveAll(genericExceptionTypes); + } + + /** + * Returns the resolved generic return type of {@code method}. + * + * @param method a method defined by this or any supertype. + * @since 2.0 + */ + public TypeLiteral getReturnType(Method method) { + checkArgument(method.getDeclaringClass().isAssignableFrom(rawType), + "%s is not defined by a supertype of %s", method, type); + return resolve(method.getGenericReturnType()); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/WeakKeySet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/WeakKeySet.java new file mode 100644 index 00000000000..97d455913af --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/WeakKeySet.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject; + +import org.elasticsearch.util.gcommon.collect.Sets; + +import java.util.Set; + +/** + * Minimal set that doesn't hold strong references to the contained keys. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +final class WeakKeySet { + + /** + * We store strings rather than keys so we don't hold strong references. + * + *

One potential problem with this approach is that parent and child injectors cannot define + * keys whose class names are equal but class loaders are different. This shouldn't be an issue + * in practice. + */ + private Set backingSet = Sets.newHashSet(); + + public boolean add(Key key) { + return backingSet.add(key.toString()); + } + + public boolean contains(Object o) { + return o instanceof Key && backingSet.contains(o.toString()); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/Assisted.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/Assisted.java new file mode 100644 index 00000000000..6a0e365aaf8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/Assisted.java @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import org.elasticsearch.util.guice.inject.BindingAnnotation; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates an injected parameter or field whose value comes from an argument to a factory method. + * + * @author jmourits@google.com (Jerome Mourits) + * @author jessewilson@google.com (Jesse Wilson) + */ +@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) +public @interface Assisted { + + /** + * The unique name for this parameter. This is matched to the {@literal @Assisted} constructor + * parameter with the same value. Names are not necessary when the parameter types are distinct. + */ + String value() default ""; +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/AssistedConstructor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/AssistedConstructor.java new file mode 100644 index 00000000000..8ceff0b33b7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/AssistedConstructor.java @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Internal respresentation of a constructor annotated with + * {@link AssistedInject} + * + * @author jmourits@google.com (Jerome Mourits) + * @author jessewilson@google.com (Jesse Wilson) + */ +class AssistedConstructor { + + private final Constructor constructor; + private final ParameterListKey assistedParameters; + private final List allParameters; + + @SuppressWarnings("unchecked") + public AssistedConstructor(Constructor constructor, List> parameterTypes) { + this.constructor = constructor; + + Annotation[][] annotations = constructor.getParameterAnnotations(); + + List typeList = Lists.newArrayList(); + allParameters = new ArrayList(); + + // categorize params as @Assisted or @Injected + for (int i = 0; i < parameterTypes.size(); i++) { + Parameter parameter = new Parameter(parameterTypes.get(i).getType(), annotations[i]); + allParameters.add(parameter); + if (parameter.isProvidedByFactory()) { + typeList.add(parameter.getType()); + } + } + this.assistedParameters = new ParameterListKey(typeList); + } + + /** + * Returns the {@link ParameterListKey} for this constructor. The + * {@link ParameterListKey} is created from the ordered list of {@link Assisted} + * constructor parameters. + */ + public ParameterListKey getAssistedParameters() { + return assistedParameters; + } + + /** + * Returns an ordered list of all constructor parameters (both + * {@link Assisted} and {@link Inject}ed). + */ + public List getAllParameters() { + return allParameters; + } + + public Set> getDeclaredExceptions() { + return new HashSet>(Arrays.asList(constructor.getExceptionTypes())); + } + + /** + * Returns an instance of T, constructed using this constructor, with the + * supplied arguments. + */ + public T newInstance(Object[] args) throws Throwable { + constructor.setAccessible(true); + try { + return constructor.newInstance(args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + @Override + public String toString() { + return constructor.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/AssistedInject.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/AssistedInject.java new file mode 100644 index 00000000000..d9e7876052c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/AssistedInject.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + *

Constructors annotated with {@code @AssistedInject} indicate that they can be instantiated by + * the {@link FactoryProvider}. Each constructor must exactly match one corresponding factory method + * within the factory interface. + * + *

Constructor parameters must be either supplied by the factory interface and marked with + * @Assisted, or they must be injectable. + * + * @deprecated {@link FactoryProvider} now works better with the standard {@literal @Inject} + * annotation. When using that annotation, parameters are matched by name and type rather than + * by position. In addition, values that use the standard {@literal @Inject} constructor + * annotation are eligible for method interception. + * + * @author jmourits@google.com (Jerome Mourits) + * @author jessewilson@google.com (Jesse Wilson) + */ +@Target({CONSTRUCTOR}) +@Retention(RUNTIME) +@Deprecated +public @interface AssistedInject {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/FactoryProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/FactoryProvider.java new file mode 100644 index 00000000000..dc0a2ab6117 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/FactoryProvider.java @@ -0,0 +1,348 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.HasDependencies; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.gcommon.collect.ImmutableMap; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Maps; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Provides a factory that combines the caller's arguments with injector-supplied values to + * construct objects. + * + *

Defining a factory

+ * Create an interface whose methods return the constructed type, or any of its supertypes. The + * method's parameters are the arguments required to build the constructed type. + *
public interface PaymentFactory {
+ *   Payment create(Date startDate, Money amount);
+ * }
+ * You can name your factory methods whatever you like, such as create, createPayment + * or newPayment. + * + *

Creating a type that accepts factory parameters

+ * {@code constructedType} is a concrete class with an {@literal @}{@link Inject}-annotated + * constructor. In addition to injector-supplied parameters, the constructor should have + * parameters that match each of the factory method's parameters. Each factory-supplied parameter + * requires an {@literal @}{@link Assisted} annotation. This serves to document that the parameter + * is not bound by your application's modules. + *
public class RealPayment implements Payment {
+ *   {@literal @}Inject
+ *   public RealPayment(
+ *      CreditService creditService,
+ *      AuthService authService,
+ *      {@literal @}Assisted Date startDate,
+ *      {@literal @}Assisted Money amount) {
+ *     ...
+ *   }
+ * }
+ * Any parameter that permits a null value should also be annotated {@code @Nullable}. + * + *

Configuring factories

+ * In your {@link org.elasticsearch.util.guice.inject.Module module}, bind the factory interface to the returned + * factory: + *
bind(PaymentFactory.class).toProvider(
+ *     FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));
+ * As a side-effect of this binding, Guice will inject the factory to initialize it for use. The + * factory cannot be used until the injector has been initialized. + * + *

Using the factory

+ * Inject your factory into your application classes. When you use the factory, your arguments + * will be combined with values from the injector to construct an instance. + *
public class PaymentAction {
+ *   {@literal @}Inject private PaymentFactory paymentFactory;
+ *
+ *   public void doPayment(Money amount) {
+ *     Payment payment = paymentFactory.create(new Date(), amount);
+ *     payment.apply();
+ *   }
+ * }
+ * + *

Making parameter types distinct

+ * The types of the factory method's parameters must be distinct. To use multiple parameters of + * the same type, use a named {@literal @}{@link Assisted} annotation to disambiguate the + * parameters. The names must be applied to the factory method's parameters: + * + *
public interface PaymentFactory {
+ *   Payment create(
+ *       {@literal @}Assisted("startDate") Date startDate,
+ *       {@literal @}Assisted("dueDate") Date dueDate,
+ *       Money amount);
+ * } 
+ * ...and to the concrete type's constructor parameters: + *
public class RealPayment implements Payment {
+ *   {@literal @}Inject
+ *   public RealPayment(
+ *      CreditService creditService,
+ *      AuthService authService,
+ *      {@literal @}Assisted("startDate") Date startDate,
+ *      {@literal @}Assisted("dueDate") Date dueDate,
+ *      {@literal @}Assisted Money amount) {
+ *     ...
+ *   }
+ * }
+ * + *

Values are created by Guice

+ * Returned factories use child injectors to create values. The values are eligible for method + * interception. In addition, {@literal @}{@literal Inject} members will be injected before they are + * returned. + * + *

Backwards compatibility using {@literal @}AssistedInject

+ * Instead of the {@literal @}Inject annotation, you may annotate the constructed classes with + * {@literal @}{@link AssistedInject}. This triggers a limited backwards-compatability mode. + * + *

Instead of matching factory method arguments to constructor parameters using their names, the + * parameters are matched by their order. The first factory method argument is + * used for the first {@literal @}Assisted constructor parameter, etc.. Annotation names have no + * effect. + * + *

Returned values are not created by Guice. These types are not eligible for + * method interception. They do receive post-construction member injection. + * + * @param The factory interface + * + * @author jmourits@google.com (Jerome Mourits) + * @author jessewilson@google.com (Jesse Wilson) + * @author dtm@google.com (Daniel Martin) + */ +public class FactoryProvider implements Provider, HasDependencies { + + /* + * This class implements the old @AssistedInject implementation that manually matches constructors + * to factory methods. The new child injector implementation lives in FactoryProvider2. + */ + + private Injector injector; + + private final TypeLiteral factoryType; + private final Map> factoryMethodToConstructor; + + public static Provider newFactory( + Class factoryType, Class implementationType){ + return newFactory(TypeLiteral.get(factoryType), TypeLiteral.get(implementationType)); + } + + public static Provider newFactory( + TypeLiteral factoryType, TypeLiteral implementationType) { + Map> factoryMethodToConstructor + = createMethodMapping(factoryType, implementationType); + + if (!factoryMethodToConstructor.isEmpty()) { + return new FactoryProvider(factoryType, factoryMethodToConstructor); + } else { + return new FactoryProvider2(factoryType, Key.get(implementationType)); + } + } + + private FactoryProvider(TypeLiteral factoryType, + Map> factoryMethodToConstructor) { + this.factoryType = factoryType; + this.factoryMethodToConstructor = factoryMethodToConstructor; + checkDeclaredExceptionsMatch(); + } + + @Inject + void setInjectorAndCheckUnboundParametersAreInjectable(Injector injector) { + this.injector = injector; + for (AssistedConstructor c : factoryMethodToConstructor.values()) { + for (Parameter p : c.getAllParameters()) { + if(!p.isProvidedByFactory() && !paramCanBeInjected(p, injector)) { + // this is lame - we're not using the proper mechanism to add an + // error to the injector. Throughout this class we throw exceptions + // to add errors, which isn't really the best way in Guice + throw newConfigurationException("Parameter of type '%s' is not injectable or annotated " + + "with @Assisted for Constructor '%s'", p, c); + } + } + } + } + + private void checkDeclaredExceptionsMatch() { + for (Map.Entry> entry : factoryMethodToConstructor.entrySet()) { + for (Class constructorException : entry.getValue().getDeclaredExceptions()) { + if (!isConstructorExceptionCompatibleWithFactoryExeception( + constructorException, entry.getKey().getExceptionTypes())) { + throw newConfigurationException("Constructor %s declares an exception, but no compatible " + + "exception is thrown by the factory method %s", entry.getValue(), entry.getKey()); + } + } + } + } + + private boolean isConstructorExceptionCompatibleWithFactoryExeception( + Class constructorException, Class[] factoryExceptions) { + for (Class factoryException : factoryExceptions) { + if (factoryException.isAssignableFrom(constructorException)) { + return true; + } + } + return false; + } + + private boolean paramCanBeInjected(Parameter parameter, Injector injector) { + return parameter.isBound(injector); + } + + private static Map> createMethodMapping( + TypeLiteral factoryType, TypeLiteral implementationType) { + List> constructors = Lists.newArrayList(); + + for (Constructor constructor : implementationType.getRawType().getDeclaredConstructors()) { + if (constructor.getAnnotation(AssistedInject.class) != null) { + @SuppressWarnings("unchecked") // the constructor type and implementation type agree + AssistedConstructor assistedConstructor = new AssistedConstructor( + constructor, implementationType.getParameterTypes(constructor)); + constructors.add(assistedConstructor); + } + } + + if (constructors.isEmpty()) { + return ImmutableMap.of(); + } + + Method[] factoryMethods = factoryType.getRawType().getMethods(); + + if (constructors.size() != factoryMethods.length) { + throw newConfigurationException("Constructor mismatch: %s has %s @AssistedInject " + + "constructors, factory %s has %s creation methods", implementationType, + constructors.size(), factoryType, factoryMethods.length); + } + + Map paramsToConstructor = Maps.newHashMap(); + + for (AssistedConstructor c : constructors) { + if (paramsToConstructor.containsKey(c.getAssistedParameters())) { + throw new RuntimeException("Duplicate constructor, " + c); + } + paramsToConstructor.put(c.getAssistedParameters(), c); + } + + Map> result = Maps.newHashMap(); + for (Method method : factoryMethods) { + if (!method.getReturnType().isAssignableFrom(implementationType.getRawType())) { + throw newConfigurationException("Return type of method %s is not assignable from %s", + method, implementationType); + } + + List parameterTypes = Lists.newArrayList(); + for (TypeLiteral parameterType : factoryType.getParameterTypes(method)) { + parameterTypes.add(parameterType.getType()); + } + ParameterListKey methodParams = new ParameterListKey(parameterTypes); + + if (!paramsToConstructor.containsKey(methodParams)) { + throw newConfigurationException("%s has no @AssistInject constructor that takes the " + + "@Assisted parameters %s in that order. @AssistInject constructors are %s", + implementationType, methodParams, paramsToConstructor.values()); + } + + method.getParameterAnnotations(); + for (Annotation[] parameterAnnotations : method.getParameterAnnotations()) { + for (Annotation parameterAnnotation : parameterAnnotations) { + if (parameterAnnotation.annotationType() == Assisted.class) { + throw newConfigurationException("Factory method %s has an @Assisted parameter, which " + + "is incompatible with the deprecated @AssistedInject annotation. Please replace " + + "@AssistedInject with @Inject on the %s constructor.", + method, implementationType); + } + } + } + + AssistedConstructor matchingConstructor = paramsToConstructor.remove(methodParams); + + result.put(method, matchingConstructor); + } + return result; + } + + public Set> getDependencies() { + List> dependencies = Lists.newArrayList(); + for (AssistedConstructor constructor : factoryMethodToConstructor.values()) { + for (Parameter parameter : constructor.getAllParameters()) { + if (!parameter.isProvidedByFactory()) { + dependencies.add(Dependency.get(parameter.getPrimaryBindingKey())); + } + } + } + return ImmutableSet.copyOf(dependencies); + } + + public F get() { + InvocationHandler invocationHandler = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] creationArgs) throws Throwable { + // pass methods from Object.class to the proxy + if (method.getDeclaringClass().equals(Object.class)) { + return method.invoke(this, creationArgs); + } + + AssistedConstructor constructor = factoryMethodToConstructor.get(method); + Object[] constructorArgs = gatherArgsForConstructor(constructor, creationArgs); + Object objectToReturn = constructor.newInstance(constructorArgs); + injector.injectMembers(objectToReturn); + return objectToReturn; + } + + public Object[] gatherArgsForConstructor( + AssistedConstructor constructor, + Object[] factoryArgs) { + int numParams = constructor.getAllParameters().size(); + int argPosition = 0; + Object[] result = new Object[numParams]; + + for (int i = 0; i < numParams; i++) { + Parameter parameter = constructor.getAllParameters().get(i); + if (parameter.isProvidedByFactory()) { + result[i] = factoryArgs[argPosition]; + argPosition++; + } else { + result[i] = parameter.getValue(injector); + } + } + return result; + } + }; + + @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class + Class factoryRawType = (Class) factoryType.getRawType(); + return factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(), + new Class[] { factoryRawType }, invocationHandler)); + } + + private static ConfigurationException newConfigurationException(String format, Object... args) { + return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/FactoryProvider2.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/FactoryProvider2.java new file mode 100644 index 00000000000..1b33fbe335f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/FactoryProvider2.java @@ -0,0 +1,256 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Module; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.ProvisionException; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import static org.elasticsearch.util.guice.inject.internal.Annotations.getKey; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import static org.elasticsearch.util.gcommon.collect.Iterables.getOnlyElement; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.util.Providers; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableMap; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.List; + +/** + * The newer implementation of factory provider. This implementation uses a child injector to + * create values. + * + * @author jessewilson@google.com (Jesse Wilson) + * @author dtm@google.com (Daniel Martin) + */ +final class FactoryProvider2 implements InvocationHandler, Provider { + + /** if a factory method parameter isn't annotated, it gets this annotation. */ + static final Assisted DEFAULT_ANNOTATION = new Assisted() { + public String value() { + return ""; + } + + public Class annotationType() { + return Assisted.class; + } + + @Override public boolean equals(Object o) { + return o instanceof Assisted + && ((Assisted) o).value().equals(""); + } + + @Override public int hashCode() { + return 127 * "value".hashCode() ^ "".hashCode(); + } + + @Override public String toString() { + return "@" + Assisted.class.getName() + "(value=)"; + } + }; + + /** the produced type, or null if all methods return concrete types */ + private final Key producedType; + private final ImmutableMap> returnTypesByMethod; + private final ImmutableMap>> paramTypes; + + /** the hosting injector, or null if we haven't been initialized yet */ + private Injector injector; + + /** the factory interface, implemented and provided */ + private final F factory; + + /** + * @param factoryType a Java interface that defines one or more create methods. + * @param producedType a concrete type that is assignable to the return types of all factory + * methods. + */ + FactoryProvider2(TypeLiteral factoryType, Key producedType) { + this.producedType = producedType; + + Errors errors = new Errors(); + + @SuppressWarnings("unchecked") // we imprecisely treat the class literal of T as a Class + Class factoryRawType = (Class) factoryType.getRawType(); + + try { + ImmutableMap.Builder> returnTypesBuilder = ImmutableMap.builder(); + ImmutableMap.Builder>> paramTypesBuilder + = ImmutableMap.builder(); + // TODO: also grab methods from superinterfaces + for (Method method : factoryRawType.getMethods()) { + Key returnType = getKey( + factoryType.getReturnType(method), method, method.getAnnotations(), errors); + returnTypesBuilder.put(method, returnType); + List> params = factoryType.getParameterTypes(method); + Annotation[][] paramAnnotations = method.getParameterAnnotations(); + int p = 0; + List> keys = Lists.newArrayList(); + for (TypeLiteral param : params) { + Key paramKey = getKey(param, method, paramAnnotations[p++], errors); + keys.add(assistKey(method, paramKey, errors)); + } + paramTypesBuilder.put(method, ImmutableList.copyOf(keys)); + } + returnTypesByMethod = returnTypesBuilder.build(); + paramTypes = paramTypesBuilder.build(); + } catch (ErrorsException e) { + throw new ConfigurationException(e.getErrors().getMessages()); + } + + factory = factoryRawType.cast(Proxy.newProxyInstance(factoryRawType.getClassLoader(), + new Class[] { factoryRawType }, this)); + } + + public F get() { + return factory; + } + + /** + * Returns a key similar to {@code key}, but with an {@literal @}Assisted binding annotation. + * This fails if another binding annotation is clobbered in the process. If the key already has + * the {@literal @}Assisted annotation, it is returned as-is to preserve any String value. + */ + private Key assistKey(Method method, Key key, Errors errors) throws ErrorsException { + if (key.getAnnotationType() == null) { + return Key.get(key.getTypeLiteral(), DEFAULT_ANNOTATION); + } else if (key.getAnnotationType() == Assisted.class) { + return key; + } else { + errors.withSource(method).addMessage( + "Only @Assisted is allowed for factory parameters, but found @%s", + key.getAnnotationType()); + throw errors.toException(); + } + } + + /** + * At injector-creation time, we initialize the invocation handler. At this time we make sure + * all factory methods will be able to build the target types. + */ + @Inject + void initialize(Injector injector) { + if (this.injector != null) { + throw new ConfigurationException(ImmutableList.of(new Message(FactoryProvider2.class, + "Factories.create() factories may only be used in one Injector!"))); + } + + this.injector = injector; + + for (Method method : returnTypesByMethod.keySet()) { + Object[] args = new Object[method.getParameterTypes().length]; + Arrays.fill(args, "dummy object for validating Factories"); + getBindingFromNewInjector(method, args); // throws if the binding isn't properly configured + } + } + + /** + * Creates a child injector that binds the args, and returns the binding for the method's result. + */ + public Binding getBindingFromNewInjector(final Method method, final Object[] args) { + checkState(injector != null, + "Factories.create() factories cannot be used until they're initialized by Guice."); + + final Key returnType = returnTypesByMethod.get(method); + + Module assistedModule = new AbstractModule() { + @SuppressWarnings("unchecked") // raw keys are necessary for the args array and return value + protected void configure() { + Binder binder = binder().withSource(method); + + int p = 0; + for (Key paramKey : paramTypes.get(method)) { + // Wrap in a Provider to cover null, and to prevent Guice from injecting the parameter + binder.bind((Key) paramKey).toProvider(Providers.of(args[p++])); + } + + if (producedType != null && !returnType.equals(producedType)) { + binder.bind(returnType).to((Key) producedType); + } else { + binder.bind(returnType); + } + } + }; + + Injector forCreate = injector.createChildInjector(assistedModule); + return forCreate.getBinding(returnType); + } + + /** + * When a factory method is invoked, we create a child injector that binds all parameters, then + * use that to get an instance of the return type. + */ + public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { + if (method.getDeclaringClass() == Object.class) { + return method.invoke(this, args); + } + + Provider provider = getBindingFromNewInjector(method, args).getProvider(); + try { + return provider.get(); + } catch (ProvisionException e) { + // if this is an exception declared by the factory method, throw it as-is + if (e.getErrorMessages().size() == 1) { + Message onlyError = getOnlyElement(e.getErrorMessages()); + Throwable cause = onlyError.getCause(); + if (cause != null && canRethrow(method, cause)) { + throw cause; + } + } + throw e; + } + } + + @Override public String toString() { + return factory.getClass().getInterfaces()[0].getName() + + " for " + producedType.getTypeLiteral(); + } + + @Override public boolean equals(Object o) { + return o == this || o == factory; + } + + /** Returns true if {@code thrown} can be thrown by {@code invoked} without wrapping. */ + static boolean canRethrow(Method invoked, Throwable thrown) { + if (thrown instanceof Error || thrown instanceof RuntimeException) { + return true; + } + + for (Class declared : invoked.getExceptionTypes()) { + if (declared.isInstance(thrown)) { + return true; + } + } + + return false; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/Parameter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/Parameter.java new file mode 100644 index 00000000000..a68bbfd269e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/Parameter.java @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import org.elasticsearch.util.guice.inject.BindingAnnotation; +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Models a method or constructor parameter. + * + * @author jmourits@google.com (Jerome Mourits) + * @author jessewilson@google.com (Jesse Wilson) + */ +class Parameter { + + private final Type type; + private final boolean isAssisted; + private final Annotation bindingAnnotation; + private final boolean isProvider; + + public Parameter(Type type, Annotation[] annotations) { + this.type = type; + this.bindingAnnotation = getBindingAnnotation(annotations); + this.isAssisted = hasAssistedAnnotation(annotations); + this.isProvider = isProvider(type); + } + + public boolean isProvidedByFactory() { + return isAssisted; + } + + public Type getType() { + return type; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + if (isAssisted) { + result.append("@Assisted"); + result.append(" "); + } + if (bindingAnnotation != null) { + result.append(bindingAnnotation.toString()); + result.append(" "); + } + result.append(type.toString()); + return result.toString(); + } + + private boolean hasAssistedAnnotation(Annotation[] annotations) { + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(Assisted.class)) { + return true; + } + } + return false; + } + + /** + * Returns the Guice {@link Key} for this parameter. + */ + public Object getValue(Injector injector) { + return isProvider + ? injector.getProvider(getBindingForType(getProvidedType(type))) + : injector.getInstance(getPrimaryBindingKey()); + } + + public boolean isBound(Injector injector) { + return isBound(injector, getPrimaryBindingKey()) + || isBound(injector, fixAnnotations(getPrimaryBindingKey())); + } + + private boolean isBound(Injector injector, Key key) { + // This method is particularly lame - we really need an API that can test + // for any binding, implicit or explicit + try { + return injector.getBinding(key) != null; + } catch (ConfigurationException e) { + return false; + } + } + + /** + * Replace annotation instances with annotation types, this is only + * appropriate for testing if a key is bound and not for injecting. + * + * See Guice bug 125, + * http://code.google.com/p/google-guice/issues/detail?id=125 + */ + public Key fixAnnotations(Key key) { + return key.getAnnotation() == null + ? key + : Key.get(key.getTypeLiteral(), key.getAnnotation().annotationType()); + } + + Key getPrimaryBindingKey() { + return isProvider + ? getBindingForType(getProvidedType(type)) + : getBindingForType(type); + } + + private Type getProvidedType(Type type) { + return ((ParameterizedType) type).getActualTypeArguments()[0]; + } + + private boolean isProvider(Type type) { + return type instanceof ParameterizedType + && ((ParameterizedType) type).getRawType() == Provider.class; + } + + private Key getBindingForType(Type type) { + return bindingAnnotation != null + ? Key.get(type, bindingAnnotation) + : Key.get(type); + } + + /** + * Returns the unique binding annotation from the specified list, or + * {@code null} if there are none. + * + * @throws IllegalStateException if multiple binding annotations exist. + */ + private Annotation getBindingAnnotation(Annotation[] annotations) { + Annotation bindingAnnotation = null; + for (Annotation a : annotations) { + if (a.annotationType().getAnnotation(BindingAnnotation.class) != null) { + checkArgument(bindingAnnotation == null, + "Parameter has multiple binding annotations: %s and %s", bindingAnnotation, a); + bindingAnnotation = a; + } + } + return bindingAnnotation; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/ParameterListKey.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/ParameterListKey.java new file mode 100644 index 00000000000..fa8f8c02500 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/ParameterListKey.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.assistedinject; + +import org.elasticsearch.util.guice.inject.TypeLiteral; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A list of {@link TypeLiteral}s to match an injectable Constructor's assited + * parameter types to the corresponding factory method. + * + * @author jmourits@google.com (Jerome Mourits) + * @author jessewilson@google.com (Jesse Wilson) + */ +class ParameterListKey { + + private final List paramList; + + public ParameterListKey(List paramList) { + this.paramList = new ArrayList(paramList); + } + + public ParameterListKey(Type[] types) { + this(Arrays.asList(types)); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ParameterListKey)) { + return false; + } + ParameterListKey other = (ParameterListKey) o; + return paramList.equals(other.paramList); + } + + @Override + public int hashCode() { + return paramList.hashCode(); + } + + @Override + public String toString() { + return paramList.toString(); + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/package-info.java new file mode 100644 index 00000000000..83dc89cec86 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/assistedinject/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +/** + * Extension for combining factory interfaces with injection; this extension requires {@code + * guice-jndi-2.0.jar}. + */ +package org.elasticsearch.util.guice.inject.assistedinject; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedBindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedBindingBuilder.java new file mode 100644 index 00000000000..2b6253a43f3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedBindingBuilder.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.binder; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface AnnotatedBindingBuilder extends LinkedBindingBuilder { + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + LinkedBindingBuilder annotatedWith( + Class annotationType); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + LinkedBindingBuilder annotatedWith(Annotation annotation); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedConstantBindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedConstantBindingBuilder.java new file mode 100644 index 00000000000..c88d5bb23b7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedConstantBindingBuilder.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.binder; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface AnnotatedConstantBindingBuilder { + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ConstantBindingBuilder annotatedWith( + Class annotationType); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ConstantBindingBuilder annotatedWith(Annotation annotation); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedElementBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedElementBuilder.java new file mode 100644 index 00000000000..1cfa34a202f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/AnnotatedElementBuilder.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.binder; + +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface AnnotatedElementBuilder { + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + void annotatedWith(Class annotationType); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + void annotatedWith(Annotation annotation); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/ConstantBindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/ConstantBindingBuilder.java new file mode 100644 index 00000000000..aa953ba0e15 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/ConstantBindingBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.binder; + +/** + * Binds to a constant value. + */ +public interface ConstantBindingBuilder { + + /** + * Binds constant to the given value. + */ + void to(String value); + + /** + * Binds constant to the given value. + */ + void to(int value); + + /** + * Binds constant to the given value. + */ + void to(long value); + + /** + * Binds constant to the given value. + */ + void to(boolean value); + + /** + * Binds constant to the given value. + */ + void to(double value); + + /** + * Binds constant to the given value. + */ + void to(float value); + + /** + * Binds constant to the given value. + */ + void to(short value); + + /** + * Binds constant to the given value. + */ + void to(char value); + + /** + * Binds constant to the given value. + */ + void to(Class value); + + /** + * Binds constant to the given value. + */ + > void to(E value); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/LinkedBindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/LinkedBindingBuilder.java new file mode 100644 index 00000000000..d3951a05d19 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/LinkedBindingBuilder.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.binder; + +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.TypeLiteral; + +/** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface LinkedBindingBuilder extends ScopedBindingBuilder { + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ScopedBindingBuilder to(Class implementation); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ScopedBindingBuilder to(TypeLiteral implementation); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ScopedBindingBuilder to(Key targetKey); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @see org.elasticsearch.util.guice.inject.Injector#injectMembers + */ + void toInstance(T instance); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @see org.elasticsearch.util.guice.inject.Injector#injectMembers + */ + ScopedBindingBuilder toProvider(Provider provider); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ScopedBindingBuilder toProvider( + Class> providerType); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + ScopedBindingBuilder toProvider( + Key> providerKey); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/ScopedBindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/ScopedBindingBuilder.java new file mode 100644 index 00000000000..eb66f61b507 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/ScopedBindingBuilder.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.binder; + +import org.elasticsearch.util.guice.inject.Scope; +import java.lang.annotation.Annotation; + +/** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface ScopedBindingBuilder { + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + void in(Class scopeAnnotation); + + /** + * See the EDSL examples at {@link org.elasticsearch.util.guice.inject.Binder}. + */ + void in(Scope scope); + + /** + * Instructs the {@link org.elasticsearch.util.guice.inject.Injector} to eagerly initialize this + * singleton-scoped binding upon creation. Useful for application + * initialization logic. See the EDSL examples at + * {@link org.elasticsearch.util.guice.inject.Binder}. + */ + void asEagerSingleton(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/package-info.java new file mode 100644 index 00000000000..4842be7ee58 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/binder/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Interfaces which make up {@link org.elasticsearch.util.guice.inject.Binder}'s + * expression language. + */ +package org.elasticsearch.util.guice.inject.binder; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/AbstractBindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/AbstractBindingBuilder.java new file mode 100644 index 00000000000..fa6a34f58c3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/AbstractBindingBuilder.java @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Scope; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.InstanceBinding; +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * Bind a value or constant. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public abstract class AbstractBindingBuilder { + + public static final String IMPLEMENTATION_ALREADY_SET = "Implementation is set more than once."; + public static final String SINGLE_INSTANCE_AND_SCOPE + = "Setting the scope is not permitted when binding to a single instance."; + public static final String SCOPE_ALREADY_SET = "Scope is set more than once."; + public static final String BINDING_TO_NULL = "Binding to null instances is not allowed. " + + "Use toProvider(Providers.of(null)) if this is your intended behaviour."; + public static final String CONSTANT_VALUE_ALREADY_SET = "Constant value is set more than once."; + public static final String ANNOTATION_ALREADY_SPECIFIED + = "More than one annotation is specified for this binding."; + + protected static final Key NULL_KEY = Key.get(Void.class); + + protected List elements; + protected int position; + protected final Binder binder; + private BindingImpl binding; + + public AbstractBindingBuilder(Binder binder, List elements, Object source, Key key) { + this.binder = binder; + this.elements = elements; + this.position = elements.size(); + this.binding = new UntargettedBindingImpl(source, key, Scoping.UNSCOPED); + elements.add(position, this.binding); + } + + protected BindingImpl getBinding() { + return binding; + } + + protected BindingImpl setBinding(BindingImpl binding) { + this.binding = binding; + elements.set(position, binding); + return binding; + } + + /** Sets the binding to a copy with the specified annotation on the bound key */ + protected BindingImpl annotatedWithInternal(Class annotationType) { + checkNotNull(annotationType, "annotationType"); + checkNotAnnotated(); + return setBinding(binding.withKey( + Key.get(this.binding.getKey().getTypeLiteral(), annotationType))); + } + + /** Sets the binding to a copy with the specified annotation on the bound key */ + protected BindingImpl annotatedWithInternal(Annotation annotation) { + checkNotNull(annotation, "annotation"); + checkNotAnnotated(); + return setBinding(binding.withKey( + Key.get(this.binding.getKey().getTypeLiteral(), annotation))); + } + + public void in(final Class scopeAnnotation) { + checkNotNull(scopeAnnotation, "scopeAnnotation"); + checkNotScoped(); + setBinding(getBinding().withScoping(Scoping.forAnnotation(scopeAnnotation))); + } + + public void in(final Scope scope) { + checkNotNull(scope, "scope"); + checkNotScoped(); + setBinding(getBinding().withScoping(Scoping.forInstance(scope))); + } + + public void asEagerSingleton() { + checkNotScoped(); + setBinding(getBinding().withScoping(Scoping.EAGER_SINGLETON)); + } + + protected boolean keyTypeIsSet() { + return !Void.class.equals(binding.getKey().getTypeLiteral().getType()); + } + + protected void checkNotTargetted() { + if (!(binding instanceof UntargettedBindingImpl)) { + binder.addError(IMPLEMENTATION_ALREADY_SET); + } + } + + protected void checkNotAnnotated() { + if (binding.getKey().getAnnotationType() != null) { + binder.addError(ANNOTATION_ALREADY_SPECIFIED); + } + } + + protected void checkNotScoped() { + // Scoping isn't allowed when we have only one instance. + if (binding instanceof InstanceBinding) { + binder.addError(SINGLE_INSTANCE_AND_SCOPE); + return; + } + + if (binding.getScoping().isExplicitlyScoped()) { + binder.addError(SCOPE_ALREADY_SET); + } + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Annotations.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Annotations.java new file mode 100644 index 00000000000..8ba3f15e2eb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Annotations.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.BindingAnnotation; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.ScopeAnnotation; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.Classes; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Member; + +/** + * Annotation utilities. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Annotations { + + /** + * Returns true if the given annotation is retained at runtime. + */ + public static boolean isRetainedAtRuntime(Class annotationType) { + Retention retention = annotationType.getAnnotation(Retention.class); + return retention != null && retention.value() == RetentionPolicy.RUNTIME; + } + + /** Returns the scope annotation on {@code type}, or null if none is specified. */ + public static Class findScopeAnnotation( + Errors errors, Class implementation) { + return findScopeAnnotation(errors, implementation.getAnnotations()); + } + + /** Returns the scoping annotation, or null if there isn't one. */ + public static Class findScopeAnnotation(Errors errors, Annotation[] annotations) { + Class found = null; + + for (Annotation annotation : annotations) { + if (annotation.annotationType().isAnnotationPresent(ScopeAnnotation.class)) { + if (found != null) { + errors.duplicateScopeAnnotations(found, annotation.annotationType()); + } else { + found = annotation.annotationType(); + } + } + } + + return found; + } + + public static boolean isScopeAnnotation(Class annotationType) { + return annotationType.isAnnotationPresent(ScopeAnnotation.class); + } + + /** + * Adds an error if there is a misplaced annotations on {@code type}. Scoping + * annotations are not allowed on abstract classes or interfaces. + */ + public static void checkForMisplacedScopeAnnotations( + Class type, Object source, Errors errors) { + if (Classes.isConcrete(type)) { + return; + } + + Class scopeAnnotation = findScopeAnnotation(errors, type); + if (scopeAnnotation != null) { + errors.withSource(type).scopeAnnotationOnAbstractType(scopeAnnotation, type, source); + } + } + + /** Gets a key for the given type, member and annotations. */ + public static Key getKey(TypeLiteral type, Member member, Annotation[] annotations, + Errors errors) throws ErrorsException { + int numErrorsBefore = errors.size(); + Annotation found = findBindingAnnotation(errors, member, annotations); + errors.throwIfNewErrors(numErrorsBefore); + return found == null ? Key.get(type) : Key.get(type, found); + } + + /** + * Returns the binding annotation on {@code member}, or null if there isn't one. + */ + public static Annotation findBindingAnnotation( + Errors errors, Member member, Annotation[] annotations) { + Annotation found = null; + + for (Annotation annotation : annotations) { + if (annotation.annotationType().isAnnotationPresent(BindingAnnotation.class)) { + if (found != null) { + errors.duplicateBindingAnnotations(member, + found.annotationType(), annotation.annotationType()); + } else { + found = annotation; + } + } + } + + return found; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/AsynchronousComputationException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/AsynchronousComputationException.java new file mode 100644 index 00000000000..674acca4624 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/AsynchronousComputationException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +/** + * Wraps an exception that occured during a computation in a different thread. + * + * @author Bob Lee + */ +public class AsynchronousComputationException extends ComputationException { + + public AsynchronousComputationException(Throwable cause) { + super(cause); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BindingBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BindingBuilder.java new file mode 100644 index 00000000000..400b921a981 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BindingBuilder.java @@ -0,0 +1,136 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.binder.AnnotatedBindingBuilder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Set; + +/** + * Bind a non-constant key. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class BindingBuilder extends AbstractBindingBuilder + implements AnnotatedBindingBuilder { + + public BindingBuilder(Binder binder, List elements, Object source, Key key) { + super(binder, elements, source, key); + } + + public BindingBuilder annotatedWith(Class annotationType) { + annotatedWithInternal(annotationType); + return this; + } + + public BindingBuilder annotatedWith(Annotation annotation) { + annotatedWithInternal(annotation); + return this; + } + + public BindingBuilder to(Class implementation) { + return to(Key.get(implementation)); + } + + public BindingBuilder to(TypeLiteral implementation) { + return to(Key.get(implementation)); + } + + public BindingBuilder to(Key linkedKey) { + checkNotNull(linkedKey, "linkedKey"); + checkNotTargetted(); + BindingImpl base = getBinding(); + setBinding(new LinkedBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), linkedKey)); + return this; + } + + public void toInstance(T instance) { + checkNotTargetted(); + + // lookup the injection points, adding any errors to the binder's errors list + Set injectionPoints; + if (instance != null) { + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(instance.getClass()); + } catch (ConfigurationException e) { + for (Message message : e.getErrorMessages()) { + binder.addError(message); + } + injectionPoints = e.getPartialValue(); + } + } else { + binder.addError(BINDING_TO_NULL); + injectionPoints = ImmutableSet.of(); + } + + BindingImpl base = getBinding(); + setBinding(new InstanceBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), injectionPoints, instance)); + } + + public BindingBuilder toProvider(Provider provider) { + checkNotNull(provider, "provider"); + checkNotTargetted(); + + // lookup the injection points, adding any errors to the binder's errors list + Set injectionPoints; + try { + injectionPoints = InjectionPoint.forInstanceMethodsAndFields(provider.getClass()); + } catch (ConfigurationException e) { + for (Message message : e.getErrorMessages()) { + binder.addError(message); + } + injectionPoints = e.getPartialValue(); + } + + BindingImpl base = getBinding(); + setBinding(new ProviderInstanceBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), injectionPoints, provider)); + return this; + } + + public BindingBuilder toProvider(Class> providerType) { + return toProvider(Key.get(providerType)); + } + + public BindingBuilder toProvider(Key> providerKey) { + checkNotNull(providerKey, "providerKey"); + checkNotTargetted(); + + BindingImpl base = getBinding(); + setBinding(new LinkedProviderBindingImpl( + base.getSource(), base.getKey(), base.getScoping(), providerKey)); + return this; + } + + @Override public String toString() { + return "BindingBuilder<" + getBinding().getKey().getTypeLiteral() + ">"; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BindingImpl.java new file mode 100644 index 00000000000..0a2b3f39943 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BindingImpl.java @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.spi.BindingScopingVisitor; +import org.elasticsearch.util.guice.inject.spi.ElementVisitor; +import org.elasticsearch.util.guice.inject.spi.InstanceBinding; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public abstract class BindingImpl implements Binding { + + private final Injector injector; + private final Key key; + private final Object source; + private final Scoping scoping; + private final InternalFactory internalFactory; + + public BindingImpl(Injector injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping) { + this.injector = injector; + this.key = key; + this.source = source; + this.internalFactory = internalFactory; + this.scoping = scoping; + } + + protected BindingImpl(Object source, Key key, Scoping scoping) { + this.internalFactory = null; + this.injector = null; + this.source = source; + this.key = key; + this.scoping = scoping; + } + + public Key getKey() { + return key; + } + + public Object getSource() { + return source; + } + + private volatile Provider provider; + + public Provider getProvider() { + if (provider == null) { + if (injector == null) { + throw new UnsupportedOperationException("getProvider() not supported for module bindings"); + } + + provider = injector.getProvider(key); + } + return provider; + } + + public InternalFactory getInternalFactory() { + return internalFactory; + } + + public Scoping getScoping() { + return scoping; + } + + /** + * Is this a constant binding? This returns true for constant bindings as + * well as toInstance() bindings. + */ + public boolean isConstant() { + return this instanceof InstanceBinding; + } + + public V acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public V acceptScopingVisitor(BindingScopingVisitor visitor) { + return scoping.acceptVisitor(visitor); + } + + protected BindingImpl withScoping(Scoping scoping) { + throw new AssertionError(); + } + + protected BindingImpl withKey(Key key) { + throw new AssertionError(); + } + + @Override public String toString() { + return new ToStringBuilder(Binding.class) + .add("key", key) + .add("scope", scoping) + .add("source", source) + .toString(); + } + + public Injector getInjector() { + return injector; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BytecodeGen.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BytecodeGen.java new file mode 100644 index 00000000000..9825377e824 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/BytecodeGen.java @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.collect.MapMaker; + +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; +import java.util.logging.Logger; + +/** + * Utility methods for runtime code generation and class loading. We use this stuff for {@link + * net.sf.cglib.reflect.FastClass faster reflection}, {@link net.sf.cglib.proxy.Enhancer method + * interceptors} and to proxy circular dependencies. + * + *

When loading classes, we need to be careful of: + *

    + *
  • Memory leaks. Generated classes need to be garbage collected in long-lived + * applications. Once an injector and any instances it created can be garbage collected, the + * corresponding generated classes should be collectable. + *
  • Visibility. Containers like OSGi use class loader boundaries + * to enforce modularity at runtime. + *
+ * + *

For each generated class, there's multiple class loaders involved: + *

    + *
  • The related class's class loader. Every generated class services exactly + * one user-supplied class. This class loader must be used to access members with private and + * package visibility. + *
  • Guice's class loader. + *
  • Our bridge class loader. This is a child of the user's class loader. It + * selectively delegates to either the user's class loader (for user classes) or the Guice + * class loader (for internal classes that are used by the generated classes). This class + * loader that owns the classes generated by Guice. + *
+ * + * @author mcculls@gmail.com (Stuart McCulloch) + * @author jessewilson@google.com (Jesse Wilson) + */ +public final class BytecodeGen { + + private static final Logger logger = Logger.getLogger(BytecodeGen.class.getName()); + + static final ClassLoader GUICE_CLASS_LOADER = BytecodeGen.class.getClassLoader(); + + /** ie. "com.google.inject.internal" */ + private static final String GUICE_INTERNAL_PACKAGE + = BytecodeGen.class.getName().replaceFirst("\\.internal\\..*$", ".internal"); + + private static final String CGLIB_PACKAGE = " "; // any string that's illegal in a package name + + /** Use "-Dguice.custom.loader=false" to disable custom classloading. */ + static final boolean HOOK_ENABLED + = "true".equals(System.getProperty("guice.custom.loader", "true")); + + /** + * Weak cache of bridge class loaders that make the Guice implementation + * classes visible to various code-generated proxies of client classes. + */ + private static final Map CLASS_LOADER_CACHE + = new MapMaker().weakKeys().weakValues().makeComputingMap( + new Function() { + public ClassLoader apply(final @Nullable ClassLoader typeClassLoader) { + logger.fine("Creating a bridge ClassLoader for " + typeClassLoader); + return AccessController.doPrivileged(new PrivilegedAction() { + public ClassLoader run() { + return new BridgeClassLoader(typeClassLoader); + } + }); + } + }); + + /** + * For class loaders, {@code null}, is always an alias to the + * {@link ClassLoader#getSystemClassLoader() system class loader}. This method + * will not return null. + */ + private static ClassLoader canonicalize(ClassLoader classLoader) { + return classLoader != null + ? classLoader + : checkNotNull(getSystemClassLoaderOrNull(), "Couldn't get a ClassLoader"); + } + + /** + * Returns the system classloader, or {@code null} if we don't have + * permission. + */ + private static ClassLoader getSystemClassLoaderOrNull() { + try { + return ClassLoader.getSystemClassLoader(); + } catch (SecurityException e) { + return null; + } + } + + /** + * Returns the class loader to host generated classes for {@code type}. + */ + public static ClassLoader getClassLoader(Class type) { + return getClassLoader(type, type.getClassLoader()); + } + + private static ClassLoader getClassLoader(Class type, ClassLoader delegate) { + delegate = canonicalize(delegate); + + // if the application is running in the System classloader, assume we can run there too + if (delegate == getSystemClassLoaderOrNull()) { + return delegate; + } + + // Don't bother bridging existing bridge classloaders + if (delegate instanceof BridgeClassLoader) { + return delegate; + } + + if (HOOK_ENABLED && Visibility.forType(type) == Visibility.PUBLIC) { + return CLASS_LOADER_CACHE.get(delegate); + } + + return delegate; + } + + /** + * The required visibility of a user's class from a Guice-generated class. Visibility of + * package-private members depends on the loading classloader: only if two classes were loaded by + * the same classloader can they see each other's package-private members. We need to be careful + * when choosing which classloader to use for generated classes. We prefer our bridge classloader, + * since it's OSGi-safe and doesn't leak permgen space. But often we cannot due to visibility. + */ + public enum Visibility { + + /** + * Indicates that Guice-generated classes only need to call and override public members of the + * target class. These generated classes may be loaded by our bridge classloader. + */ + PUBLIC { + public Visibility and(Visibility that) { + return that; + } + }, + + /** + * Indicates that Guice-generated classes need to call or override package-private members. + * These generated classes must be loaded in the same classloader as the target class. They + * won't work with OSGi, and won't get garbage collected until the target class' classloader is + * garbage collected. + */ + SAME_PACKAGE { + public Visibility and(Visibility that) { + return this; + } + }; + + public static Visibility forMember(Member member) { + if ((member.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) == 0) { + return SAME_PACKAGE; + } + + Class[] parameterTypes = member instanceof Constructor + ? ((Constructor) member).getParameterTypes() + : ((Method) member).getParameterTypes(); + for (Class type : parameterTypes) { + if (forType(type) == SAME_PACKAGE) { + return SAME_PACKAGE; + } + } + + return PUBLIC; + } + + public static Visibility forType(Class type) { + return (type.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) != 0 + ? PUBLIC + : SAME_PACKAGE; + } + + public abstract Visibility and(Visibility that); + } + + /** + * Loader for Guice-generated classes. For referenced classes, this delegates to either either the + * user's classloader (which is the parent of this classloader) or Guice's class loader. + */ + private static class BridgeClassLoader extends ClassLoader { + + public BridgeClassLoader(ClassLoader usersClassLoader) { + super(usersClassLoader); + } + + @Override protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + + // delegate internal requests to Guice class space + if (name.startsWith(GUICE_INTERNAL_PACKAGE) || name.startsWith(CGLIB_PACKAGE)) { + try { + Class clazz = GUICE_CLASS_LOADER.loadClass(name); + if (resolve) { + resolveClass(clazz); + } + return clazz; + } catch (Exception e) { + // fall back to classic delegation + } + } + + return super.loadClass(name, resolve); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Collections2.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Collections2.java new file mode 100644 index 00000000000..4eaf78a841f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Collections2.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.Collection; +import java.util.Set; + +/** + * Provides static methods for working with {@code Collection} instances. + * + * @author Chris Povirk + * @author Mike Bostock + * @author Jared Levy + */ +public final class Collections2 { + private Collections2() {} + + /** + * Converts an iterable into a collection. If the iterable is already a + * collection, it is returned. Otherwise, an {@link java.util.ArrayList} is + * created with the contents of the iterable in same iteration order. + */ + static Collection toCollection(Iterable iterable) { + return (iterable instanceof Collection) + ? (Collection) iterable : Lists.newArrayList(iterable); + } + + static boolean setEquals(Set thisSet, @Nullable Object object) { + if (object == thisSet) { + return true; + } + if (object instanceof Set) { + Set thatSet = (Set) object; + return thisSet.size() == thatSet.size() + && thisSet.containsAll(thatSet); + } + return false; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ComputationException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ComputationException.java new file mode 100644 index 00000000000..962ca3ad483 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ComputationException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +/** + * Wraps an exception that occured during a computation. + */ +public class ComputationException extends RuntimeException { + + public ComputationException(Throwable cause) { + super(cause); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ConstantBindingBuilderImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ConstantBindingBuilderImpl.java new file mode 100644 index 00000000000..4e30f657c65 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ConstantBindingBuilderImpl.java @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.binder.AnnotatedConstantBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.ConstantBindingBuilder; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * Bind a constant. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public final class ConstantBindingBuilderImpl + extends AbstractBindingBuilder + implements AnnotatedConstantBindingBuilder, ConstantBindingBuilder { + + @SuppressWarnings("unchecked") // constant bindings start out with T unknown + public ConstantBindingBuilderImpl(Binder binder, List elements, Object source) { + super(binder, elements, source, (Key) NULL_KEY); + } + + public ConstantBindingBuilder annotatedWith(Class annotationType) { + annotatedWithInternal(annotationType); + return this; + } + + public ConstantBindingBuilder annotatedWith(Annotation annotation) { + annotatedWithInternal(annotation); + return this; + } + + public void to(final String value) { + toConstant(String.class, value); + } + + public void to(final int value) { + toConstant(Integer.class, value); + } + + public void to(final long value) { + toConstant(Long.class, value); + } + + public void to(final boolean value) { + toConstant(Boolean.class, value); + } + + public void to(final double value) { + toConstant(Double.class, value); + } + + public void to(final float value) { + toConstant(Float.class, value); + } + + public void to(final short value) { + toConstant(Short.class, value); + } + + public void to(final char value) { + toConstant(Character.class, value); + } + + public void to(final Class value) { + toConstant(Class.class, value); + } + + public > void to(final E value) { + toConstant(value.getDeclaringClass(), value); + } + + private void toConstant(Class type, Object instance) { + // this type will define T, so these assignments are safe + @SuppressWarnings("unchecked") + Class typeAsClassT = (Class) type; + @SuppressWarnings("unchecked") + T instanceAsT = (T) instance; + + if (keyTypeIsSet()) { + binder.addError(CONSTANT_VALUE_ALREADY_SET); + return; + } + + BindingImpl base = getBinding(); + Key key; + if (base.getKey().getAnnotation() != null) { + key = Key.get(typeAsClassT, base.getKey().getAnnotation()); + } else if (base.getKey().getAnnotationType() != null) { + key = Key.get(typeAsClassT, base.getKey().getAnnotationType()); + } else { + key = Key.get(typeAsClassT); + } + + if (instanceAsT == null) { + binder.addError(BINDING_TO_NULL); + } + + setBinding(new InstanceBindingImpl( + base.getSource(), key, base.getScoping(), ImmutableSet.of(), instanceAsT)); + } + + @Override public String toString() { + return "ConstantBindingBuilder"; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ConstructionContext.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ConstructionContext.java new file mode 100644 index 00000000000..14ace7a9059 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ConstructionContext.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +/** + * Context of a dependency construction. Used to manage circular references. + * + * @author crazybob@google.com (Bob Lee) + */ +public class ConstructionContext { + + T currentReference; + boolean constructing; + + List> invocationHandlers; + + public T getCurrentReference() { + return currentReference; + } + + public void removeCurrentReference() { + this.currentReference = null; + } + + public void setCurrentReference(T currentReference) { + this.currentReference = currentReference; + } + + public boolean isConstructing() { + return constructing; + } + + public void startConstruction() { + this.constructing = true; + } + + public void finishConstruction() { + this.constructing = false; + invocationHandlers = null; + } + + public Object createProxy(Errors errors, Class expectedType) throws ErrorsException { + // TODO: if I create a proxy which implements all the interfaces of + // the implementation type, I'll be able to get away with one proxy + // instance (as opposed to one per caller). + + if (!expectedType.isInterface()) { + throw errors.cannotSatisfyCircularDependency(expectedType).toException(); + } + + if (invocationHandlers == null) { + invocationHandlers = new ArrayList>(); + } + + DelegatingInvocationHandler invocationHandler + = new DelegatingInvocationHandler(); + invocationHandlers.add(invocationHandler); + + ClassLoader classLoader = BytecodeGen.getClassLoader(expectedType); + return expectedType.cast(Proxy.newProxyInstance(classLoader, + new Class[] { expectedType }, invocationHandler)); + } + + public void setProxyDelegates(T delegate) { + if (invocationHandlers != null) { + for (DelegatingInvocationHandler handler : invocationHandlers) { + handler.setDelegate(delegate); + } + } + } + + static class DelegatingInvocationHandler implements InvocationHandler { + + T delegate; + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (delegate == null) { + throw new IllegalStateException("This is a proxy used to support" + + " circular references involving constructors. The object we're" + + " proxying is not constructed yet. Please wait until after" + + " injection has completed to use this object."); + } + + try { + // This appears to be not test-covered + return method.invoke(delegate, args); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + void setDelegate(T delegate) { + this.delegate = delegate; + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ErrorHandler.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ErrorHandler.java new file mode 100644 index 00000000000..987f32b9960 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ErrorHandler.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.spi.Message; + +/** + * Handles errors in the Injector. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface ErrorHandler { + + /** + * Handles an error. + */ + void handle(Object source, Errors errors); + + /** + * Handles a user-reported error. + */ + void handle(Message message); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Errors.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Errors.java new file mode 100644 index 00000000000..eb744141923 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Errors.java @@ -0,0 +1,646 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.CreationException; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.MembersInjector; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.ProvisionException; +import org.elasticsearch.util.guice.inject.Scope; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.InjectionListener; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.spi.TypeListenerBinding; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Formatter; +import java.util.List; + +/** + * A collection of error messages. If this type is passed as a method parameter, the method is + * considered to have executed succesfully only if new errors were not added to this collection. + * + *

Errors can be chained to provide additional context. To add context, call {@link #withSource} + * to create a new Errors instance that contains additional context. All messages added to the + * returned instance will contain full context. + * + *

To avoid messages with redundant context, {@link #withSource} should be added sparingly. A + * good rule of thumb is to assume a ethod's caller has already specified enough context to + * identify that method. When calling a method that's defined in a different context, call that + * method with an errors object that includes its context. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public final class Errors implements Serializable { + + /** + * The root errors object. Used to access the list of error messages. + */ + private final Errors root; + + /** + * The parent errors object. Used to obtain the chain of source objects. + */ + private final Errors parent; + + /** + * The leaf source for errors added here. + */ + private final Object source; + + /** + * null unless (root == this) and error messages exist. Never an empty list. + */ + private List errors; // lazy, use getErrorsForAdd() + + public Errors() { + this.root = this; + this.parent = null; + this.source = SourceProvider.UNKNOWN_SOURCE; + } + + public Errors(Object source) { + this.root = this; + this.parent = null; + this.source = source; + } + + private Errors(Errors parent, Object source) { + this.root = parent.root; + this.parent = parent; + this.source = source; + } + + /** + * Returns an instance that uses {@code source} as a reference point for newly added errors. + */ + public Errors withSource(Object source) { + return source == SourceProvider.UNKNOWN_SOURCE + ? this + : new Errors(this, source); + } + + /** + * We use a fairly generic error message here. The motivation is to share the + * same message for both bind time errors: + *

Guice.createInjector(new AbstractModule() {
+   *   public void configure() {
+   *     bind(Runnable.class);
+   *   }
+   * }
+ * ...and at provide-time errors: + *
Guice.createInjector().getInstance(Runnable.class);
+ * Otherwise we need to know who's calling when resolving a just-in-time + * binding, which makes things unnecessarily complex. + */ + public Errors missingImplementation(Key key) { + return addMessage("No implementation for %s was bound.", key); + } + + public Errors converterReturnedNull(String stringValue, Object source, + TypeLiteral type, MatcherAndConverter matchingConverter) { + return addMessage("Received null converting '%s' (bound at %s) to %s%n" + + " using %s.", + stringValue, convert(source), type, matchingConverter); + } + + public Errors conversionTypeError(String stringValue, Object source, TypeLiteral type, + MatcherAndConverter matchingConverter, Object converted) { + return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n" + + " using %s.%n" + + " Converter returned %s.", + stringValue, convert(source), type, matchingConverter, converted); + } + + public Errors conversionError(String stringValue, Object source, + TypeLiteral type, MatcherAndConverter matchingConverter, RuntimeException cause) { + return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n" + + " using %s.%n" + + " Reason: %s", + stringValue, convert(source), type, matchingConverter, cause); + } + + public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral type, + MatcherAndConverter a, MatcherAndConverter b) { + return addMessage("Multiple converters can convert '%s' (bound at %s) to %s:%n" + + " %s and%n" + + " %s.%n" + + " Please adjust your type converter configuration to avoid overlapping matches.", + stringValue, convert(source), type, a, b); + } + + public Errors bindingToProvider() { + return addMessage("Binding to Provider is not allowed."); + } + + public Errors subtypeNotProvided(Class> providerType, + Class type) { + return addMessage("%s doesn't provide instances of %s.", providerType, type); + } + + public Errors notASubtype(Class implementationType, Class type) { + return addMessage("%s doesn't extend %s.", implementationType, type); + } + + public Errors recursiveImplementationType() { + return addMessage("@ImplementedBy points to the same class it annotates."); + } + + public Errors recursiveProviderType() { + return addMessage("@ProvidedBy points to the same class it annotates."); + } + + public Errors missingRuntimeRetention(Object source) { + return addMessage("Please annotate with @Retention(RUNTIME).%n" + + " Bound at %s.", convert(source)); + } + + public Errors missingScopeAnnotation() { + return addMessage("Please annotate with @ScopeAnnotation."); + } + + public Errors optionalConstructor(Constructor constructor) { + return addMessage("%s is annotated @Inject(optional=true), " + + "but constructors cannot be optional.", constructor); + } + + public Errors cannotBindToGuiceType(String simpleName) { + return addMessage("Binding to core guice framework type is not allowed: %s.", simpleName); + } + + public Errors scopeNotFound(Class scopeAnnotation) { + return addMessage("No scope is bound to %s.", scopeAnnotation); + } + + public Errors scopeAnnotationOnAbstractType( + Class scopeAnnotation, Class type, Object source) { + return addMessage("%s is annotated with %s, but scope annotations are not supported " + + "for abstract types.%n Bound at %s.", type, scopeAnnotation, convert(source)); + } + + public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) { + return addMessage("%s is annotated with %s, but binding annotations should be applied " + + "to its parameters instead.", member, bindingAnnotation); + } + + private static final String CONSTRUCTOR_RULES = + "Classes must have either one (and only one) constructor " + + "annotated with @Inject or a zero-argument constructor that is not private."; + + public Errors missingConstructor(Class implementation) { + return addMessage("Could not find a suitable constructor in %s. " + CONSTRUCTOR_RULES, + implementation); + } + + public Errors tooManyConstructors(Class implementation) { + return addMessage("%s has more than one constructor annotated with @Inject. " + + CONSTRUCTOR_RULES, implementation); + } + + public Errors duplicateScopes(Scope existing, + Class annotationType, Scope scope) { + return addMessage("Scope %s is already bound to %s. Cannot bind %s.", existing, + annotationType, scope); + } + + public Errors voidProviderMethod() { + return addMessage("Provider methods must return a value. Do not return void."); + } + + public Errors missingConstantValues() { + return addMessage("Missing constant value. Please call to(...)."); + } + + public Errors cannotInjectInnerClass(Class type) { + return addMessage("Injecting into inner classes is not supported. " + + "Please use a 'static' class (top-level or nested) instead of %s.", type); + } + + public Errors duplicateBindingAnnotations(Member member, + Class a, Class b) { + return addMessage("%s has more than one annotation annotated with @BindingAnnotation: " + + "%s and %s", member, a, b); + } + + public Errors duplicateScopeAnnotations( + Class a, Class b) { + return addMessage("More than one scope annotation was found: %s and %s.", a, b); + } + + public Errors recursiveBinding() { + return addMessage("Binding points to itself."); + } + + public Errors bindingAlreadySet(Key key, Object source) { + return addMessage("A binding to %s was already configured at %s.", key, convert(source)); + } + + public Errors childBindingAlreadySet(Key key) { + return addMessage("A binding to %s already exists on a child injector.", key); + } + + public Errors errorInjectingMethod(Throwable cause) { + return errorInUserCode(cause, "Error injecting method, %s", cause); + } + + public Errors errorNotifyingTypeListener(TypeListenerBinding listener, + TypeLiteral type, Throwable cause) { + return errorInUserCode(cause, + "Error notifying TypeListener %s (bound at %s) of %s.%n" + + " Reason: %s", + listener.getListener(), convert(listener.getSource()), type, cause); + } + + public Errors errorInjectingConstructor(Throwable cause) { + return errorInUserCode(cause, "Error injecting constructor, %s", cause); + } + + public Errors errorInProvider(RuntimeException runtimeException) { + return errorInUserCode(runtimeException, "Error in custom provider, %s", runtimeException); + } + + public Errors errorInUserInjector( + MembersInjector listener, TypeLiteral type, RuntimeException cause) { + return errorInUserCode(cause, "Error injecting %s using %s.%n" + + " Reason: %s", type, listener, cause); + } + + public Errors errorNotifyingInjectionListener( + InjectionListener listener, TypeLiteral type, RuntimeException cause) { + return errorInUserCode(cause, "Error notifying InjectionListener %s of %s.%n" + + " Reason: %s", listener, type, cause); + } + + public void exposedButNotBound(Key key) { + addMessage("Could not expose() %s, it must be explicitly bound.", key); + } + + public static Collection getMessagesFromThrowable(Throwable throwable) { + if (throwable instanceof ProvisionException) { + return ((ProvisionException) throwable).getErrorMessages(); + } else if (throwable instanceof ConfigurationException) { + return ((ConfigurationException) throwable).getErrorMessages(); + } else if (throwable instanceof CreationException) { + return ((CreationException) throwable).getErrorMessages(); + } else { + return ImmutableSet.of(); + } + } + + public Errors errorInUserCode(Throwable cause, String messageFormat, Object... arguments) { + Collection messages = getMessagesFromThrowable(cause); + + if (!messages.isEmpty()) { + return merge(messages); + } else { + return addMessage(cause, messageFormat, arguments); + } + } + + public Errors cannotInjectRawProvider() { + return addMessage("Cannot inject a Provider that has no type parameter"); + } + + public Errors cannotInjectRawMembersInjector() { + return addMessage("Cannot inject a MembersInjector that has no type parameter"); + } + + public Errors cannotInjectTypeLiteralOf(Type unsupportedType) { + return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType); + } + + public Errors cannotInjectRawTypeLiteral() { + return addMessage("Cannot inject a TypeLiteral that has no type parameter"); + } + + public Errors cannotSatisfyCircularDependency(Class expectedType) { + return addMessage( + "Tried proxying %s to support a circular dependency, but it is not an interface.", + expectedType); + } + + public void throwCreationExceptionIfErrorsExist() { + if (!hasErrors()) { + return; + } + + throw new CreationException(getMessages()); + } + + public void throwConfigurationExceptionIfErrorsExist() { + if (!hasErrors()) { + return; + } + + throw new ConfigurationException(getMessages()); + } + + public void throwProvisionExceptionIfErrorsExist() { + if (!hasErrors()) { + return; + } + + throw new ProvisionException(getMessages()); + } + + private Message merge(Message message) { + List sources = Lists.newArrayList(); + sources.addAll(getSources()); + sources.addAll(message.getSources()); + return new Message(sources, message.getMessage(), message.getCause()); + } + + public Errors merge(Collection messages) { + for (Message message : messages) { + addMessage(merge(message)); + } + return this; + } + + public Errors merge(Errors moreErrors) { + if (moreErrors.root == root || moreErrors.root.errors == null) { + return this; + } + + merge(moreErrors.root.errors); + return this; + } + + public List getSources() { + List sources = Lists.newArrayList(); + for (Errors e = this; e != null; e = e.parent) { + if (e.source != SourceProvider.UNKNOWN_SOURCE) { + sources.add(0, e.source); + } + } + return sources; + } + + public void throwIfNewErrors(int expectedSize) throws ErrorsException { + if (size() == expectedSize) { + return; + } + + throw toException(); + } + + public ErrorsException toException() { + return new ErrorsException(this); + } + + public boolean hasErrors() { + return root.errors != null; + } + + public Errors addMessage(String messageFormat, Object... arguments) { + return addMessage(null, messageFormat, arguments); + } + + private Errors addMessage(Throwable cause, String messageFormat, Object... arguments) { + String message = format(messageFormat, arguments); + addMessage(new Message(getSources(), message, cause)); + return this; + } + + public Errors addMessage(Message message) { + if (root.errors == null) { + root.errors = Lists.newArrayList(); + } + root.errors.add(message); + return this; + } + + public static String format(String messageFormat, Object... arguments) { + for (int i = 0; i < arguments.length; i++) { + arguments[i] = Errors.convert(arguments[i]); + } + return String.format(messageFormat, arguments); + } + + public List getMessages() { + if (root.errors == null) { + return ImmutableList.of(); + } + + List result = Lists.newArrayList(root.errors); + Collections.sort(result, new Comparator() { + public int compare(Message a, Message b) { + return a.getSource().compareTo(b.getSource()); + } + }); + + return result; + } + + /** Returns the formatted message for an exception with the specified messages. */ + public static String format(String heading, Collection errorMessages) { + Formatter fmt = new Formatter().format(heading).format(":%n%n"); + int index = 1; + boolean displayCauses = getOnlyCause(errorMessages) == null; + + for (Message errorMessage : errorMessages) { + fmt.format("%s) %s%n", index++, errorMessage.getMessage()); + + List dependencies = errorMessage.getSources(); + for (int i = dependencies.size() - 1; i >= 0; i--) { + Object source = dependencies.get(i); + formatSource(fmt, source); + } + + Throwable cause = errorMessage.getCause(); + if (displayCauses && cause != null) { + StringWriter writer = new StringWriter(); + cause.printStackTrace(new PrintWriter(writer)); + fmt.format("Caused by: %s", writer.getBuffer()); + } + + fmt.format("%n"); + } + + if (errorMessages.size() == 1) { + fmt.format("1 error"); + } else { + fmt.format("%s errors", errorMessages.size()); + } + + return fmt.toString(); + } + + /** + * Returns {@code value} if it is non-null allowed to be null. Otherwise a message is added and + * an {@code ErrorsException} is thrown. + */ + public T checkForNull(T value, Object source, Dependency dependency) + throws ErrorsException { + if (value != null || dependency.isNullable()) { + return value; + } + + int parameterIndex = dependency.getParameterIndex(); + String parameterName = (parameterIndex != -1) + ? "parameter " + parameterIndex + " of " + : ""; + addMessage("null returned by binding at %s%n but %s%s is not @Nullable", + source, parameterName, dependency.getInjectionPoint().getMember()); + + throw toException(); + } + + /** + * Returns the cause throwable if there is exactly one cause in {@code messages}. If there are + * zero or multiple messages with causes, null is returned. + */ + public static Throwable getOnlyCause(Collection messages) { + Throwable onlyCause = null; + for (Message message : messages) { + Throwable messageCause = message.getCause(); + if (messageCause == null) { + continue; + } + + if (onlyCause != null) { + return null; + } + + onlyCause = messageCause; + } + + return onlyCause; + } + + public int size() { + return root.errors == null ? 0 : root.errors.size(); + } + + private static abstract class Converter { + + final Class type; + + Converter(Class type) { + this.type = type; + } + + boolean appliesTo(Object o) { + return type.isAssignableFrom(o.getClass()); + } + + String convert(Object o) { + return toString(type.cast(o)); + } + + abstract String toString(T t); + } + + private static final Collection> converters = ImmutableList.of( + new Converter(Class.class) { + public String toString(Class c) { + return c.getName(); + } + }, + new Converter(Member.class) { + public String toString(Member member) { + return MoreTypes.toString(member); + } + }, + new Converter(Key.class) { + public String toString(Key key) { + if (key.getAnnotationType() != null) { + return key.getTypeLiteral() + " annotated with " + + (key.getAnnotation() != null ? key.getAnnotation() : key.getAnnotationType()); + } else { + return key.getTypeLiteral().toString(); + } + } + }); + + public static Object convert(Object o) { + for (Converter converter : converters) { + if (converter.appliesTo(o)) { + return converter.convert(o); + } + } + return o; + } + + public static void formatSource(Formatter formatter, Object source) { + if (source instanceof Dependency) { + Dependency dependency = (Dependency) source; + InjectionPoint injectionPoint = dependency.getInjectionPoint(); + if (injectionPoint != null) { + formatInjectionPoint(formatter, dependency, injectionPoint); + } else { + formatSource(formatter, dependency.getKey()); + } + + } else if (source instanceof InjectionPoint) { + formatInjectionPoint(formatter, null, (InjectionPoint) source); + + } else if (source instanceof Class) { + formatter.format(" at %s%n", StackTraceElements.forType((Class) source)); + + } else if (source instanceof Member) { + formatter.format(" at %s%n", StackTraceElements.forMember((Member) source)); + + } else if (source instanceof TypeLiteral) { + formatter.format(" while locating %s%n", source); + + } else if (source instanceof Key) { + Key key = (Key) source; + formatter.format(" while locating %s%n", convert(key)); + + } else { + formatter.format(" at %s%n", source); + } + } + + public static void formatInjectionPoint(Formatter formatter, Dependency dependency, + InjectionPoint injectionPoint) { + Member member = injectionPoint.getMember(); + Class memberType = MoreTypes.memberType(member); + + if (memberType == Field.class) { + dependency = injectionPoint.getDependencies().get(0); + formatter.format(" while locating %s%n", convert(dependency.getKey())); + formatter.format(" for field at %s%n", StackTraceElements.forMember(member)); + + } else if (dependency != null) { + formatter.format(" while locating %s%n", convert(dependency.getKey())); + formatter.format(" for parameter %s at %s%n", + dependency.getParameterIndex(), StackTraceElements.forMember(member)); + + } else { + formatSource(formatter, injectionPoint.getMember()); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ErrorsException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ErrorsException.java new file mode 100644 index 00000000000..3094071ec21 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ErrorsException.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject.internal; + +/** + * Indicates that a result could not be returned while preparing or resolving a binding. The caller + * should {@link Errors#merge(Errors) merge} the errors from this exception with their existing + * errors. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ErrorsException extends Exception { + + private final Errors errors; + + public ErrorsException(Errors errors) { + this.errors = errors; + } + + public Errors getErrors() { + return errors; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExpirationTimer.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExpirationTimer.java new file mode 100644 index 00000000000..2c8ea1a7562 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExpirationTimer.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.util.Timer; + +/** + * Timer used for entry expiration in MapMaker. + */ +class ExpirationTimer { + static Timer instance = new Timer(true); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExposedBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExposedBindingImpl.java new file mode 100644 index 00000000000..3c6c8eb276f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExposedBindingImpl.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.ExposedBinding; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Set; + +public class ExposedBindingImpl extends BindingImpl implements ExposedBinding { + + private final PrivateElements privateElements; + + public ExposedBindingImpl(Injector injector, Object source, Key key, + InternalFactory factory, PrivateElements privateElements) { + super(injector, key, source, factory, Scoping.UNSCOPED); + this.privateElements = privateElements; + } + + public ExposedBindingImpl(Object source, Key key, Scoping scoping, + PrivateElements privateElements) { + super(source, key, scoping); + this.privateElements = privateElements; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Set> getDependencies() { + return ImmutableSet.>of(Dependency.get(Key.get(Injector.class))); + } + + public PrivateElements getPrivateElements() { + return privateElements; + } + + public BindingImpl withScoping(Scoping scoping) { + return new ExposedBindingImpl(getSource(), getKey(), scoping, privateElements); + } + + public ExposedBindingImpl withKey(Key key) { + return new ExposedBindingImpl(getSource(), key, getScoping(), privateElements); + } + + @Override public String toString() { + return new ToStringBuilder(ExposedBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("privateElements", privateElements) + .toString(); + } + + public void applyTo(Binder binder) { + throw new UnsupportedOperationException("This element represents a synthetic binding."); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExposureBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExposureBuilder.java new file mode 100644 index 00000000000..7b69894c062 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ExposureBuilder.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.binder.AnnotatedElementBuilder; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Key; +import java.lang.annotation.Annotation; + +/** + * For private binder's expose() method. + */ +public class ExposureBuilder implements AnnotatedElementBuilder { + private final Binder binder; + private final Object source; + private Key key; + + public ExposureBuilder(Binder binder, Object source, Key key) { + this.binder = binder; + this.source = source; + this.key = key; + } + + protected void checkNotAnnotated() { + if (key.getAnnotationType() != null) { + binder.addError(AbstractBindingBuilder.ANNOTATION_ALREADY_SPECIFIED); + } + } + + public void annotatedWith(Class annotationType) { + org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull(annotationType, "annotationType"); + checkNotAnnotated(); + key = Key.get(key.getTypeLiteral(), annotationType); + } + + public void annotatedWith(Annotation annotation) { + org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull(annotation, "annotation"); + checkNotAnnotated(); + key = Key.get(key.getTypeLiteral(), annotation); + } + + public Key getKey() { + return key; + } + + public Object getSource() { + return source; + } + + @Override public String toString() { + return "AnnotatedElementBuilder"; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/FailableCache.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/FailableCache.java new file mode 100644 index 00000000000..1d3254c50bd --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/FailableCache.java @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.gcommon.base.Function; +import org.elasticsearch.util.gcommon.collect.MapMaker; + +import java.util.Map; + +/** + * Lazily creates (and caches) values for keys. If creating the value fails (with errors), an + * exception is thrown on retrieval. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public abstract class FailableCache { + + private final Map delegate = new MapMaker().makeComputingMap( + new Function() { + public Object apply(@Nullable K key) { + Errors errors = new Errors(); + V result = null; + try { + result = FailableCache.this.create(key, errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + return errors.hasErrors() ? errors : result; + } + }); + + protected abstract V create(K key, Errors errors) throws ErrorsException; + + public V get(K key, Errors errors) throws ErrorsException { + Object resultOrError = delegate.get(key); + if (resultOrError instanceof Errors) { + errors.merge((Errors) resultOrError); + throw errors.toException(); + } else { + @SuppressWarnings("unchecked") // create returned a non-error result, so this is safe + V result = (V) resultOrError; + return result; + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InstanceBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InstanceBindingImpl.java new file mode 100644 index 00000000000..0b14d27da6c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InstanceBindingImpl.java @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.HasDependencies; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.InstanceBinding; +import org.elasticsearch.util.guice.inject.util.Providers; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Set; + +public class InstanceBindingImpl extends BindingImpl implements InstanceBinding { + + final T instance; + final Provider provider; + final ImmutableSet injectionPoints; + + public InstanceBindingImpl(Injector injector, Key key, Object source, + InternalFactory internalFactory, Set injectionPoints, + T instance) { + super(injector, key, source, internalFactory, Scoping.UNSCOPED); + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + this.instance = instance; + this.provider = Providers.of(instance); + } + + public InstanceBindingImpl(Object source, Key key, Scoping scoping, + Set injectionPoints, T instance) { + super(source, key, scoping); + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + this.instance = instance; + this.provider = Providers.of(instance); + } + + @Override public Provider getProvider() { + return this.provider; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public T getInstance() { + return instance; + } + + public Set getInjectionPoints() { + return injectionPoints; + } + + public Set> getDependencies() { + return instance instanceof HasDependencies + ? ImmutableSet.copyOf(((HasDependencies) instance).getDependencies()) + : Dependency.forInjectionPoints(injectionPoints); + } + + public BindingImpl withScoping(Scoping scoping) { + return new InstanceBindingImpl(getSource(), getKey(), scoping, injectionPoints, instance); + } + + public BindingImpl withKey(Key key) { + return new InstanceBindingImpl(getSource(), key, getScoping(), injectionPoints, instance); + } + + public void applyTo(Binder binder) { + // instance bindings aren't scoped + binder.withSource(getSource()).bind(getKey()).toInstance(instance); + } + + @Override public String toString() { + return new ToStringBuilder(InstanceBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("instance", instance) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InternalContext.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InternalContext.java new file mode 100644 index 00000000000..6748e53c670 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InternalContext.java @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.gcommon.collect.Maps; + +import java.util.Map; + +/** + * Internal context. Used to coordinate injections and support circular + * dependencies. + * + * @author crazybob@google.com (Bob Lee) + */ +public final class InternalContext { + + private Map> constructionContexts = Maps.newHashMap(); + private Dependency dependency; + + @SuppressWarnings("unchecked") + public ConstructionContext getConstructionContext(Object key) { + ConstructionContext constructionContext + = (ConstructionContext) constructionContexts.get(key); + if (constructionContext == null) { + constructionContext = new ConstructionContext(); + constructionContexts.put(key, constructionContext); + } + return constructionContext; + } + + public Dependency getDependency() { + return dependency; + } + + public void setDependency(Dependency dependency) { + this.dependency = dependency; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InternalFactory.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InternalFactory.java new file mode 100644 index 00000000000..3bef227af32 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/InternalFactory.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.spi.Dependency; + +/** + * Creates objects which will be injected. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface InternalFactory { + + /** + * Creates an object to be injected. + * + * @param context of this injection + * @throws org.elasticsearch.util.guice.inject.internal.ErrorsException if a value cannot be provided + * @return instance to be injected + */ + T get(Errors errors, InternalContext context, Dependency dependency) + throws ErrorsException; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Join.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Join.java new file mode 100644 index 00000000000..9a831382594 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Join.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.gcommon.collect.Lists; + +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; + +/** + * Utility for joining pieces of text separated by a delimiter. It can handle + * iterators, collections, arrays, and varargs, and can append to any + * {@link Appendable} or just return a {@link String}. For example, + * {@code join(":", "a", "b", "c")} returns {@code "a:b:c"}. + * + *

All methods of this class throw {@link NullPointerException} when a value + * of {@code null} is supplied for any parameter. The elements within the + * collection, iterator, array, or varargs parameter list may be null -- + * these will be represented in the output by the string {@code "null"}. + * + * @author Kevin Bourrillion + */ +public final class Join { + private Join() {} + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it + * returns an empty string. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return a string consisting of the joined elements + */ + public static String join(String delimiter, Iterable tokens) { + return join(delimiter, tokens.iterator()); + } + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it + * returns an empty string. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return a string consisting of the joined elements + */ + public static String join(String delimiter, Object[] tokens) { + return join(delimiter, Arrays.asList(tokens)); + } + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param firstToken the first object to append + * @param otherTokens subsequent objects to append + * @return a string consisting of the joined elements + */ + public static String join( + String delimiter, @Nullable Object firstToken, Object... otherTokens) { + checkNotNull(otherTokens); + return join(delimiter, Lists.newArrayList(firstToken, otherTokens)); + } + + /** + * Returns a string containing the {@code tokens}, converted to strings if + * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it + * returns an empty string. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return a string consisting of the joined elements + */ + public static String join(String delimiter, Iterator tokens) { + StringBuilder sb = new StringBuilder(); + join(sb, delimiter, tokens); + return sb.toString(); + } + + /** + * Returns a string containing the contents of {@code map}, with entries + * separated by {@code entryDelimiter}, and keys and values separated with + * {@code keyValueSeparator}. + * + *

Each key and value will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param keyValueSeparator a string to append between every key and its + * associated value + * @param entryDelimiter a string to append between every entry, but not at + * the beginning or end + * @param map the map containing the data to join + * @return a string consisting of the joined entries of the map; empty if the + * map is empty + */ + public static String join( + String keyValueSeparator, String entryDelimiter, Map map) { + return join(new StringBuilder(), keyValueSeparator, entryDelimiter, map) + .toString(); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static T join( + T appendable, String delimiter, Iterable tokens) { + return join(appendable, delimiter, tokens.iterator()); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static T join( + T appendable, String delimiter, Object[] tokens) { + return join(appendable, delimiter, Arrays.asList(tokens)); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param firstToken the first object to append + * @param otherTokens subsequent objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static T join(T appendable, String delimiter, + @Nullable Object firstToken, Object... otherTokens) { + checkNotNull(otherTokens); + return join(appendable, delimiter, Lists.newArrayList(firstToken, otherTokens)); + } + + /** + * Appends each of the {@code tokens} to {@code appendable}, separated by + * {@code delimiter}. + * + *

Each token will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param delimiter a string to append between every element, but not at the + * beginning or end + * @param tokens objects to append + * @return the same {@code Appendable} instance that was passed in + * @throws JoinException if an {@link IOException} occurs + */ + public static T join( + T appendable, String delimiter, Iterator tokens) { + + /* This method is the workhorse of the class */ + + checkNotNull(appendable); + checkNotNull(delimiter); + if (tokens.hasNext()) { + try { + appendOneToken(appendable, tokens.next()); + while (tokens.hasNext()) { + appendable.append(delimiter); + appendOneToken(appendable, tokens.next()); + } + } catch (IOException e) { + throw new JoinException(e); + } + } + return appendable; + } + + /** + * Appends the contents of {@code map} to {@code appendable}, with entries + * separated by {@code entryDelimiter}, and keys and values separated with + * {@code keyValueSeparator}. + * + *

Each key and value will be converted to a {@link CharSequence} using + * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. + * Note that this implies that null tokens will be appended as the + * four-character string {@code "null"}. + * + * @param appendable the object to append the results to + * @param keyValueSeparator a string to append between every key and its + * associated value + * @param entryDelimiter a string to append between every entry, but not at + * the beginning or end + * @param map the map containing the data to join + * @return the same {@code Appendable} instance that was passed in + */ + public static T join(T appendable, + String keyValueSeparator, String entryDelimiter, Map map) { + checkNotNull(appendable); + checkNotNull(keyValueSeparator); + checkNotNull(entryDelimiter); + Iterator> entries = map.entrySet().iterator(); + if (entries.hasNext()) { + try { + appendOneEntry(appendable, keyValueSeparator, entries.next()); + while (entries.hasNext()) { + appendable.append(entryDelimiter); + appendOneEntry(appendable, keyValueSeparator, entries.next()); + } + } catch (IOException e) { + throw new JoinException(e); + } + } + return appendable; + } + + private static void appendOneEntry( + Appendable appendable, String keyValueSeparator, Map.Entry entry) + throws IOException { + appendOneToken(appendable, entry.getKey()); + appendable.append(keyValueSeparator); + appendOneToken(appendable, entry.getValue()); + } + + private static void appendOneToken(Appendable appendable, Object token) + throws IOException { + appendable.append(toCharSequence(token)); + } + + private static CharSequence toCharSequence(Object token) { + return (token instanceof CharSequence) + ? (CharSequence) token + : String.valueOf(token); + } + + /** + * Exception thrown in response to an {@link IOException} from the supplied + * {@link Appendable}. This is used because most callers won't want to + * worry about catching an IOException. + */ + public static class JoinException extends RuntimeException { + private JoinException(IOException cause) { + super(cause); + } + + private static final long serialVersionUID = 1L; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/LinkedBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/LinkedBindingImpl.java new file mode 100644 index 00000000000..3dab3fa659b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/LinkedBindingImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.LinkedKeyBinding; + +public final class LinkedBindingImpl extends BindingImpl implements LinkedKeyBinding { + + final Key targetKey; + + public LinkedBindingImpl(Injector injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping, + Key targetKey) { + super(injector, key, source, internalFactory, scoping); + this.targetKey = targetKey; + } + + public LinkedBindingImpl(Object source, Key key, Scoping scoping, Key targetKey) { + super(source, key, scoping); + this.targetKey = targetKey; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Key getLinkedKey() { + return targetKey; + } + + public BindingImpl withScoping(Scoping scoping) { + return new LinkedBindingImpl(getSource(), getKey(), scoping, targetKey); + } + + public BindingImpl withKey(Key key) { + return new LinkedBindingImpl(getSource(), key, getScoping(), targetKey); + } + + public void applyTo(Binder binder) { + getScoping().applyTo(binder.withSource(getSource()).bind(getKey()).to(getLinkedKey())); + } + + @Override public String toString() { + return new ToStringBuilder(LinkedKeyBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .add("target", targetKey) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/LinkedProviderBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/LinkedProviderBindingImpl.java new file mode 100644 index 00000000000..19fb43db876 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/LinkedProviderBindingImpl.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.ProviderKeyBinding; + +public final class LinkedProviderBindingImpl + extends BindingImpl implements ProviderKeyBinding { + + final Key> providerKey; + + public LinkedProviderBindingImpl(Injector injector, Key key, Object source, + InternalFactory internalFactory, Scoping scoping, + Key> providerKey) { + super(injector, key, source, internalFactory, scoping); + this.providerKey = providerKey; + } + + LinkedProviderBindingImpl(Object source, Key key, Scoping scoping, + Key> providerKey) { + super(source, key, scoping); + this.providerKey = providerKey; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Key> getProviderKey() { + return providerKey; + } + + public BindingImpl withScoping(Scoping scoping) { + return new LinkedProviderBindingImpl(getSource(), getKey(), scoping, providerKey); + } + + public BindingImpl withKey(Key key) { + return new LinkedProviderBindingImpl(getSource(), key, getScoping(), providerKey); + } + + public void applyTo(Binder binder) { + getScoping().applyTo(binder.withSource(getSource()) + .bind(getKey()).toProvider(getProviderKey())); + } + + @Override public String toString() { + return new ToStringBuilder(ProviderKeyBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .add("provider", providerKey) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/MatcherAndConverter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/MatcherAndConverter.java new file mode 100644 index 00000000000..354f9d2615e --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/MatcherAndConverter.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.TypeLiteral; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.guice.inject.spi.TypeConverter; + +/** + * @author crazybob@google.com (Bob Lee) + */ +public final class MatcherAndConverter { + + private final Matcher> typeMatcher; + private final TypeConverter typeConverter; + private final Object source; + + public MatcherAndConverter(Matcher> typeMatcher, + TypeConverter typeConverter, Object source) { + this.typeMatcher = checkNotNull(typeMatcher, "type matcher"); + this.typeConverter = checkNotNull(typeConverter, "converter"); + this.source = source; + } + + public TypeConverter getTypeConverter() { + return typeConverter; + } + + public Matcher> getTypeMatcher() { + return typeMatcher; + } + + public Object getSource() { + return source; + } + + @Override public String toString() { + return typeConverter + " which matches " + typeMatcher + + " (bound at " + source + ")"; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/MoreTypes.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/MoreTypes.java new file mode 100644 index 00000000000..23217137e17 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/MoreTypes.java @@ -0,0 +1,660 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.gcommon.base.Objects; +import org.elasticsearch.util.gcommon.collect.ImmutableMap; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Static methods for working with types that we aren't publishing in the + * public {@code Types} API. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class MoreTypes { + + public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {}; + + private MoreTypes() {} + + private static final Map, TypeLiteral> PRIMITIVE_TO_WRAPPER + = new ImmutableMap.Builder, TypeLiteral>() + .put(TypeLiteral.get(boolean.class), TypeLiteral.get(Boolean.class)) + .put(TypeLiteral.get(byte.class), TypeLiteral.get(Byte.class)) + .put(TypeLiteral.get(short.class), TypeLiteral.get(Short.class)) + .put(TypeLiteral.get(int.class), TypeLiteral.get(Integer.class)) + .put(TypeLiteral.get(long.class), TypeLiteral.get(Long.class)) + .put(TypeLiteral.get(float.class), TypeLiteral.get(Float.class)) + .put(TypeLiteral.get(double.class), TypeLiteral.get(Double.class)) + .put(TypeLiteral.get(char.class), TypeLiteral.get(Character.class)) + .put(TypeLiteral.get(void.class), TypeLiteral.get(Void.class)) + .build(); + + /** + * Returns an equivalent type that's safe for use in a key. The returned type will be free of + * primitive types. Type literals of primitives will return the corresponding wrapper types. + * + * @throws ConfigurationException if {@code type} contains a type variable + */ + public static TypeLiteral makeKeySafe(TypeLiteral type) { + if (!isFullySpecified(type.getType())) { + String message = type + " cannot be used as a key; It is not fully specified."; + throw new ConfigurationException(ImmutableSet.of(new Message(message))); + } + + @SuppressWarnings("unchecked") + TypeLiteral wrappedPrimitives = (TypeLiteral) PRIMITIVE_TO_WRAPPER.get(type); + return wrappedPrimitives != null + ? wrappedPrimitives + : type; + } + + /** + * Returns true if {@code type} is free from type variables. + */ + private static boolean isFullySpecified(Type type) { + if (type instanceof Class) { + return true; + + } else if (type instanceof CompositeType) { + return ((CompositeType) type).isFullySpecified(); + + } else if (type instanceof TypeVariable){ + return false; + + } else { + return ((CompositeType) canonicalize(type)).isFullySpecified(); + } + } + + /** + * Returns a type that is functionally equal but not necessarily equal + * according to {@link Object#equals(Object) Object.equals()}. The returned + * type is {@link Serializable}. + */ + public static Type canonicalize(Type type) { + if (type instanceof ParameterizedTypeImpl + || type instanceof GenericArrayTypeImpl + || type instanceof WildcardTypeImpl) { + return type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) type; + return new ParameterizedTypeImpl(p.getOwnerType(), + p.getRawType(), p.getActualTypeArguments()); + + } else if (type instanceof GenericArrayType) { + GenericArrayType g = (GenericArrayType) type; + return new GenericArrayTypeImpl(g.getGenericComponentType()); + + } else if (type instanceof Class && ((Class) type).isArray()) { + Class c = (Class) type; + return new GenericArrayTypeImpl(c.getComponentType()); + + } else if (type instanceof WildcardType) { + WildcardType w = (WildcardType) type; + return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds()); + + } else { + // type is either serializable as-is or unsupported + return type; + } + } + + /** + * Returns a type that's functionally equal but not necessarily equal + * according to {@link Object#equals(Object) Object.equals}. The returned + * member is {@link Serializable}. + */ + public static Member serializableCopy(Member member) { + return member instanceof MemberImpl + ? member + : new MemberImpl(member); + } + + public static Class getRawType(Type type) { + if (type instanceof Class) { + // type is a normal class. + return (Class) type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + + // I'm not exactly sure why getRawType() returns Type instead of Class. + // Neal isn't either but suspects some pathological case related + // to nested classes exists. + Type rawType = parameterizedType.getRawType(); + checkArgument(rawType instanceof Class, + "Expected a Class, but <%s> is of type %s", type, type.getClass().getName()); + return (Class) rawType; + + } else if (type instanceof GenericArrayType) { + // TODO: Is this sufficient? + return Object[].class; + + } else if (type instanceof TypeVariable) { + // we could use the variable's bounds, but that'll won't work if there are multiple. + // having a raw type that's more general than necessary is okay + return Object.class; + + } else { + throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + + "GenericArrayType, but <" + type + "> is of type " + type.getClass().getName()); + } + } + + /** + * Returns true if {@code a} and {@code b} are equal. + */ + public static boolean equals(Type a, Type b) { + if (a == b) { + // also handles (a == null && b == null) + return true; + + } else if (a instanceof Class) { + // Class already specifies equals(). + return a.equals(b); + + } else if (a instanceof ParameterizedType) { + if (!(b instanceof ParameterizedType)) { + return false; + } + + // TODO: save a .clone() call + ParameterizedType pa = (ParameterizedType) a; + ParameterizedType pb = (ParameterizedType) b; + return Objects.equal(pa.getOwnerType(), pb.getOwnerType()) + && pa.getRawType().equals(pb.getRawType()) + && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); + + } else if (a instanceof GenericArrayType) { + if (!(b instanceof GenericArrayType)) { + return false; + } + + GenericArrayType ga = (GenericArrayType) a; + GenericArrayType gb = (GenericArrayType) b; + return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); + + } else if (a instanceof WildcardType) { + if (!(b instanceof WildcardType)) { + return false; + } + + WildcardType wa = (WildcardType) a; + WildcardType wb = (WildcardType) b; + return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) + && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); + + } else if (a instanceof TypeVariable) { + if (!(b instanceof TypeVariable)) { + return false; + } + TypeVariable va = (TypeVariable) a; + TypeVariable vb = (TypeVariable) b; + return va.getGenericDeclaration() == vb.getGenericDeclaration() + && va.getName().equals(vb.getName()); + + } else { + // This isn't a type we support. Could be a generic array type, wildcard type, etc. + return false; + } + } + + /** + * Returns the hashCode of {@code type}. + */ + public static int hashCode(Type type) { + if (type instanceof Class) { + // Class specifies hashCode(). + return type.hashCode(); + + } else if (type instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) type; + return Arrays.hashCode(p.getActualTypeArguments()) + ^ p.getRawType().hashCode() + ^ hashCodeOrZero(p.getOwnerType()); + + } else if (type instanceof GenericArrayType) { + return hashCode(((GenericArrayType) type).getGenericComponentType()); + + } else if (type instanceof WildcardType) { + WildcardType w = (WildcardType) type; + return Arrays.hashCode(w.getLowerBounds()) ^ Arrays.hashCode(w.getUpperBounds()); + + } else { + // This isn't a type we support. Probably a type variable + return hashCodeOrZero(type); + } + } + + private static int hashCodeOrZero(Object o) { + return o != null ? o.hashCode() : 0; + } + + public static String toString(Type type) { + if (type instanceof Class) { + return ((Class) type).getName(); + + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] arguments = parameterizedType.getActualTypeArguments(); + Type ownerType = parameterizedType.getOwnerType(); + StringBuilder stringBuilder = new StringBuilder(); + if (ownerType != null) { + stringBuilder.append(toString(ownerType)).append("."); + } + stringBuilder.append(toString(parameterizedType.getRawType())); + if (arguments.length > 0) { + stringBuilder + .append("<") + .append(toString(arguments[0])); + for (int i = 1; i < arguments.length; i++) { + stringBuilder.append(", ").append(toString(arguments[i])); + } + } + return stringBuilder.append(">").toString(); + + } else if (type instanceof GenericArrayType) { + return toString(((GenericArrayType) type).getGenericComponentType()) + "[]"; + + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + Type[] lowerBounds = wildcardType.getLowerBounds(); + Type[] upperBounds = wildcardType.getUpperBounds(); + + if (upperBounds.length != 1 || lowerBounds.length > 1) { + throw new UnsupportedOperationException("Unsupported wildcard type " + type); + } + + if (lowerBounds.length == 1) { + if (upperBounds[0] != Object.class) { + throw new UnsupportedOperationException("Unsupported wildcard type " + type); + } + return "? super " + toString(lowerBounds[0]); + } else if (upperBounds[0] == Object.class) { + return "?"; + } else { + return "? extends " + toString(upperBounds[0]); + } + + } else { + return type.toString(); + } + } + + /** + * Returns {@code Field.class}, {@code Method.class} or {@code Constructor.class}. + */ + public static Class memberType(Member member) { + checkNotNull(member, "member"); + + if (member instanceof MemberImpl) { + return ((MemberImpl) member).memberType; + + } else if (member instanceof Field) { + return Field.class; + + } else if (member instanceof Method) { + return Method.class; + + } else if (member instanceof Constructor) { + return Constructor.class; + + } else { + throw new IllegalArgumentException( + "Unsupported implementation class for Member, " + member.getClass()); + } + } + + /** + * Formats a member as concise string, such as {@code java.util.ArrayList.size}, + * {@code java.util.ArrayList()} or {@code java.util.List.remove()}. + */ + public static String toString(Member member) { + Class memberType = memberType(member); + + if (memberType == Method.class) { + return member.getDeclaringClass().getName() + "." + member.getName() + "()"; + } else if (memberType == Field.class) { + return member.getDeclaringClass().getName() + "." + member.getName(); + } else if (memberType == Constructor.class) { + return member.getDeclaringClass().getName() + ".()"; + } else { + throw new AssertionError(); + } + } + + public static String memberKey(Member member) { + checkNotNull(member, "member"); + + return ""; + } + + /** + * Returns the generic supertype for {@code supertype}. For example, given a class {@code + * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the + * result when the supertype is {@code Collection.class} is {@code Collection}. + */ + public static Type getGenericSupertype(Type type, Class rawType, Class toResolve) { + if (toResolve == rawType) { + return type; + } + + // we skip searching through interfaces if unknown is an interface + if (toResolve.isInterface()) { + Class[] interfaces = rawType.getInterfaces(); + for (int i = 0, length = interfaces.length; i < length; i++) { + if (interfaces[i] == toResolve) { + return rawType.getGenericInterfaces()[i]; + } else if (toResolve.isAssignableFrom(interfaces[i])) { + return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); + } + } + } + + // check our supertypes + if (!rawType.isInterface()) { + while (rawType != Object.class) { + Class rawSupertype = rawType.getSuperclass(); + if (rawSupertype == toResolve) { + return rawType.getGenericSuperclass(); + } else if (toResolve.isAssignableFrom(rawSupertype)) { + return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); + } + rawType = rawSupertype; + } + } + + // we can't resolve this further + return toResolve; + } + + public static Type resolveTypeVariable(Type type, Class rawType, TypeVariable unknown) { + Class declaredByRaw = declaringClassOf(unknown); + + // we can't reduce this further + if (declaredByRaw == null) { + return unknown; + } + + Type declaredBy = getGenericSupertype(type, rawType, declaredByRaw); + if (declaredBy instanceof ParameterizedType) { + int index = indexOf(declaredByRaw.getTypeParameters(), unknown); + return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; + } + + return unknown; + } + + private static int indexOf(Object[] array, Object toFind) { + for (int i = 0; i < array.length; i++) { + if (toFind.equals(array[i])) { + return i; + } + } + throw new NoSuchElementException(); + } + + /** + * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by + * a class. + */ + private static Class declaringClassOf(TypeVariable typeVariable) { + GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); + return genericDeclaration instanceof Class + ? (Class) genericDeclaration + : null; + } + + public static class ParameterizedTypeImpl + implements ParameterizedType, Serializable, CompositeType { + private final Type ownerType; + private final Type rawType; + private final Type[] typeArguments; + + public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { + // require an owner type if the raw type needs it + if (rawType instanceof Class) { + Class rawTypeAsClass = (Class) rawType; + checkArgument(ownerType != null || rawTypeAsClass.getEnclosingClass() == null, + "No owner type for enclosed %s", rawType); + checkArgument(ownerType == null || rawTypeAsClass.getEnclosingClass() != null, + "Owner type for unenclosed %s", rawType); + } + + this.ownerType = ownerType == null ? null : canonicalize(ownerType); + this.rawType = canonicalize(rawType); + this.typeArguments = typeArguments.clone(); + for (int t = 0; t < this.typeArguments.length; t++) { + checkNotNull(this.typeArguments[t], "type parameter"); + checkNotPrimitive(this.typeArguments[t], "type parameters"); + this.typeArguments[t] = canonicalize(this.typeArguments[t]); + } + } + + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + public Type getRawType() { + return rawType; + } + + public Type getOwnerType() { + return ownerType; + } + + public boolean isFullySpecified() { + if (ownerType != null && !MoreTypes.isFullySpecified(ownerType)) { + return false; + } + + if (!MoreTypes.isFullySpecified(rawType)) { + return false; + } + + for (Type type : typeArguments) { + if (!MoreTypes.isFullySpecified(type)) { + return false; + } + } + + return true; + } + + @Override public boolean equals(Object other) { + return other instanceof ParameterizedType + && MoreTypes.equals(this, (ParameterizedType) other); + } + + @Override public int hashCode() { + return MoreTypes.hashCode(this); + } + + @Override public String toString() { + return MoreTypes.toString(this); + } + + private static final long serialVersionUID = 0; + } + + public static class GenericArrayTypeImpl + implements GenericArrayType, Serializable, CompositeType { + private final Type componentType; + + public GenericArrayTypeImpl(Type componentType) { + this.componentType = canonicalize(componentType); + } + + public Type getGenericComponentType() { + return componentType; + } + + public boolean isFullySpecified() { + return MoreTypes.isFullySpecified(componentType); + } + + @Override public boolean equals(Object o) { + return o instanceof GenericArrayType + && MoreTypes.equals(this, (GenericArrayType) o); + } + + @Override public int hashCode() { + return MoreTypes.hashCode(this); + } + + @Override public String toString() { + return MoreTypes.toString(this); + } + + private static final long serialVersionUID = 0; + } + + /** + * The WildcardType interface supports multiple upper bounds and multiple + * lower bounds. We only support what the Java 6 language needs - at most one + * bound. If a lower bound is set, the upper bound must be Object.class. + */ + public static class WildcardTypeImpl implements WildcardType, Serializable, CompositeType { + private final Type upperBound; + private final Type lowerBound; + + public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { + checkArgument(lowerBounds.length <= 1, "Must have at most one lower bound."); + checkArgument(upperBounds.length == 1, "Must have exactly one upper bound."); + + if (lowerBounds.length == 1) { + checkNotNull(lowerBounds[0], "lowerBound"); + checkNotPrimitive(lowerBounds[0], "wildcard bounds"); + checkArgument(upperBounds[0] == Object.class, "bounded both ways"); + this.lowerBound = canonicalize(lowerBounds[0]); + this.upperBound = Object.class; + + } else { + checkNotNull(upperBounds[0], "upperBound"); + checkNotPrimitive(upperBounds[0], "wildcard bounds"); + this.lowerBound = null; + this.upperBound = canonicalize(upperBounds[0]); + } + } + + public Type[] getUpperBounds() { + return new Type[] { upperBound }; + } + + public Type[] getLowerBounds() { + return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY; + } + + public boolean isFullySpecified() { + return MoreTypes.isFullySpecified(upperBound) + && (lowerBound == null || MoreTypes.isFullySpecified(lowerBound)); + } + + @Override public boolean equals(Object other) { + return other instanceof WildcardType + && MoreTypes.equals(this, (WildcardType) other); + } + + @Override public int hashCode() { + return MoreTypes.hashCode(this); + } + + @Override public String toString() { + return MoreTypes.toString(this); + } + + private static final long serialVersionUID = 0; + } + + private static void checkNotPrimitive(Type type, String use) { + checkArgument(!(type instanceof Class) || !((Class) type).isPrimitive(), + "Primitive types are not allowed in %s: %s", use, type); + } + + /** + * We cannot serialize the built-in Java member classes, which prevents us from using Members in + * our exception types. We workaround this with this serializable implementation. It includes all + * of the API methods, plus everything we use for line numbers and messaging. + */ + public static class MemberImpl implements Member, Serializable { + private final Class declaringClass; + private final String name; + private final int modifiers; + private final boolean synthetic; + private final Class memberType; + private final String memberKey; + + private MemberImpl(Member member) { + this.declaringClass = member.getDeclaringClass(); + this.name = member.getName(); + this.modifiers = member.getModifiers(); + this.synthetic = member.isSynthetic(); + this.memberType = memberType(member); + this.memberKey = memberKey(member); + } + + public Class getDeclaringClass() { + return declaringClass; + } + + public String getName() { + return name; + } + + public int getModifiers() { + return modifiers; + } + + public boolean isSynthetic() { + return synthetic; + } + + @Override public String toString() { + return MoreTypes.toString(this); + } + } + + /** A type formed from other types, such as arrays, parameterized types or wildcard types */ + private interface CompositeType { + /** Returns true if there are no type variables in this type. */ + boolean isFullySpecified(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/NullOutputException.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/NullOutputException.java new file mode 100644 index 00000000000..146508849ab --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/NullOutputException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +/** + * Thrown when a computer function returns null. This subclass exists so + * that our ReferenceCache adapter can differentiate null output from null + * keys, but we don't want to make this public otherwise. + * + * @author Bob Lee + */ +class NullOutputException extends NullPointerException { + public NullOutputException(String s) { + super(s); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Nullability.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Nullability.java new file mode 100644 index 00000000000..109fb5631ca --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Nullability.java @@ -0,0 +1,31 @@ +package org.elasticsearch.util.guice.inject.internal; + +import java.lang.annotation.Annotation; + +/** + * Whether a member supports null values injected. + * + *

Support for {@code Nullable} annotations in Guice is loose. + * Any annotation type whose simplename is "Nullable" is sufficient to indicate + * support for null values injected. + * + *

This allows support for JSR-305's + * + * javax.annotation.meta.Nullable annotation and IntelliJ IDEA's + * + * org.jetbrains.annotations.Nullable. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class Nullability { + private Nullability() {} + + public static boolean allowsNull(Annotation[] annotations) { + for(Annotation a : annotations) { + if ("Nullable".equals(a.annotationType().getSimpleName())) { + return true; + } + } + return false; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Nullable.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Nullable.java new file mode 100644 index 00000000000..3bd56ab46d2 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Nullable.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +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; + +/** + * The presence of this annotation on a method parameter indicates that + * {@code null} is an acceptable value for that parameter. It should not be + * used for parameters of primitive types. + * + *

This annotation may be used with the Google Web Toolkit (GWT). + * + * @author Kevin Bourrillion + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.FIELD}) +public @interface Nullable { } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Preconditions.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Preconditions.java new file mode 100644 index 00000000000..1c179555c93 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Preconditions.java @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.util.Collection; +import java.util.NoSuchElementException; + +/** + * Simple static methods to be called at the start of your own methods to verify + * correct arguments and state. This allows constructs such as + *

+ *     if (count <= 0) {
+ *       throw new IllegalArgumentException("must be positive: " + count);
+ *     }
+ * + * to be replaced with the more compact + *
+ *     checkArgument(count > 0, "must be positive: %s", count);
+ * + * Note that the sense of the expression is inverted; with {@code Preconditions} + * you declare what you expect to be true, just as you do with an + * + * {@code assert} or a JUnit {@code assertTrue()} call. + * + *

Take care not to confuse precondition checking with other similar types + * of checks! Precondition exceptions -- including those provided here, but also + * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link + * UnsupportedOperationException} and others -- are used to signal that the + * calling method has made an error. This tells the caller that it should + * not have invoked the method when it did, with the arguments it did, or + * perhaps ever. Postcondition or other invariant failures should not + * throw these types of exceptions. + * + *

Note: The methods of the {@code Preconditions} class are highly + * unusual in one way: they are supposed to throw exceptions, and promise + * in their specifications to do so even when given perfectly valid input. That + * is, {@code null} is a valid parameter to the method {@link + * #checkNotNull(Object)} -- and technically this parameter could be even marked + * as {@link Nullable} -- yet the method will still throw an exception anyway, + * because that's what its contract says to do. + * + *

This class may be used with the Google Web Toolkit (GWT). + * + * @author Kevin Bourrillion + */ +public final class Preconditions { + private Preconditions() {} + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the + * calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkArgument(boolean expression, + String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling + * instance, but not involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @throws IllegalStateException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code + * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let + * this happen) + */ + public static void checkState(boolean expression, + String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException( + format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static T checkNotNull(T reference, String errorMessageTemplate, + Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException( + format(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /** + * Ensures that an {@code Iterable} object passed as a parameter to the + * calling method is not null and contains no null elements. + * + * @param iterable the iterable to check the contents of + * @return the non-null {@code iterable} reference just validated + * @throws NullPointerException if {@code iterable} is null or contains at + * least one null element + */ + public static > T checkContentsNotNull(T iterable) { + if (containsOrIsNull(iterable)) { + throw new NullPointerException(); + } + return iterable; + } + + /** + * Ensures that an {@code Iterable} object passed as a parameter to the + * calling method is not null and contains no null elements. + * + * @param iterable the iterable to check the contents of + * @param errorMessage the exception message to use if the check fails; will + * be converted to a string using {@link String#valueOf(Object)} + * @return the non-null {@code iterable} reference just validated + * @throws NullPointerException if {@code iterable} is null or contains at + * least one null element + */ + public static > T checkContentsNotNull( + T iterable, Object errorMessage) { + if (containsOrIsNull(iterable)) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return iterable; + } + + /** + * Ensures that an {@code Iterable} object passed as a parameter to the + * calling method is not null and contains no null elements. + * + * @param iterable the iterable to check the contents of + * @param errorMessageTemplate a template for the exception message should the + * check fail. The message is formed by replacing each {@code %s} + * placeholder in the template with an argument. These are matched by + * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc. + * Unmatched arguments will be appended to the formatted message in square + * braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. + * @return the non-null {@code iterable} reference just validated + * @throws NullPointerException if {@code iterable} is null or contains at + * least one null element + */ + public static > T checkContentsNotNull(T iterable, + String errorMessageTemplate, Object... errorMessageArgs) { + if (containsOrIsNull(iterable)) { + throw new NullPointerException( + format(errorMessageTemplate, errorMessageArgs)); + } + return iterable; + } + + private static boolean containsOrIsNull(Iterable iterable) { + if (iterable == null) { + return true; + } + + if (iterable instanceof Collection) { + Collection collection = (Collection) iterable; + try { + return collection.contains(null); + } catch (NullPointerException e) { + // A NPE implies that the collection doesn't contain null. + return false; + } + } else { + for (Object element : iterable) { + if (element == null) { + return true; + } + } + return false; + } + } + + /** + * Ensures that {@code index} specifies a valid element in an array, + * list or string of size {@code size}. An element index may range from zero, + * inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list + * or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if {@code index} is negative or is not + * less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkElementIndex(int index, int size) { + checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, + * list or string of size {@code size}. An element index may range from zero, + * inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list + * or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @throws IndexOutOfBoundsException if {@code index} is negative or is not + * less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkElementIndex(int index, int size, String desc) { + checkArgument(size >= 0, "negative size: %s", size); + if (index < 0) { + throw new IndexOutOfBoundsException( + format("%s (%s) must not be negative", desc, index)); + } + if (index >= size) { + throw new IndexOutOfBoundsException( + format("%s (%s) must be less than size (%s)", desc, index, size)); + } + } + + /** + * Ensures that {@code index} specifies a valid position in an array, + * list or string of size {@code size}. A position index may range from zero + * to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list + * or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if {@code index} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndex(int index, int size) { + checkPositionIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid position in an array, + * list or string of size {@code size}. A position index may range from zero + * to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list + * or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @throws IndexOutOfBoundsException if {@code index} is negative or is + * greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndex(int index, int size, String desc) { + checkArgument(size >= 0, "negative size: %s", size); + if (index < 0) { + throw new IndexOutOfBoundsException(format( + "%s (%s) must not be negative", desc, index)); + } + if (index > size) { + throw new IndexOutOfBoundsException(format( + "%s (%s) must not be greater than size (%s)", desc, index, size)); + } + } + + /** + * Ensures that {@code start} and {@code end} specify a valid positions + * in an array, list or string of size {@code size}, and are in order. A + * position index may range from zero to {@code size}, inclusive. + * + * @param start a user-supplied index identifying a starting position in an + * array, list or string + * @param end a user-supplied index identifying a ending position in an array, + * list or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if either index is negative or is + * greater than {@code size}, or if {@code end} is less than {@code start} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndexes(int start, int end, int size) { + checkPositionIndex(start, size, "start index"); + checkPositionIndex(end, size, "end index"); + if (end < start) { + throw new IndexOutOfBoundsException(format( + "end index (%s) must not be less than start index (%s)", end, start)); + } + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position - the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments will + * be appended to the end of the formatted message in square braces. + * + * @param template a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + */ + // VisibleForTesting + static String format(String template, Object... args) { + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder( + template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append("]"); + } + + return builder.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/PrivateElementsImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/PrivateElementsImpl.java new file mode 100644 index 00000000000..ba66e64ff61 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/PrivateElementsImpl.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.PrivateBinder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.ElementVisitor; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableMap; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Maps; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public final class PrivateElementsImpl implements PrivateElements { + + /* + * This class acts as both a value object and as a builder. When getElements() is called, an + * immutable collection of elements is constructed and the original mutable list is nulled out. + * Similarly, the exposed keys are made immutable on access. + */ + + private final Object source; + + private List elementsMutable = Lists.newArrayList(); + private List> exposureBuilders = Lists.newArrayList(); + + /** lazily instantiated */ + private ImmutableList elements; + + /** lazily instantiated */ + private ImmutableMap, Object> exposedKeysToSources; + private Injector injector; + + public PrivateElementsImpl(Object source) { + this.source = checkNotNull(source, "source"); + } + + public Object getSource() { + return source; + } + + public List getElements() { + if (elements == null) { + elements = ImmutableList.copyOf(elementsMutable); + elementsMutable = null; + } + + return elements; + } + + public Injector getInjector() { + return injector; + } + + public void initInjector(Injector injector) { + checkState(this.injector == null, "injector already initialized"); + this.injector = checkNotNull(injector, "injector"); + } + + public Set> getExposedKeys() { + if (exposedKeysToSources == null) { + Map, Object> exposedKeysToSourcesMutable = Maps.newLinkedHashMap(); + for (ExposureBuilder exposureBuilder : exposureBuilders) { + exposedKeysToSourcesMutable.put(exposureBuilder.getKey(), exposureBuilder.getSource()); + } + exposedKeysToSources = ImmutableMap.copyOf(exposedKeysToSourcesMutable); + exposureBuilders = null; + } + + return exposedKeysToSources.keySet(); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public List getElementsMutable() { + return elementsMutable; + } + + public void addExposureBuilder(ExposureBuilder exposureBuilder) { + exposureBuilders.add(exposureBuilder); + } + + public void applyTo(Binder binder) { + PrivateBinder privateBinder = binder.withSource(source).newPrivateBinder(); + + for (Element element : getElements()) { + element.applyTo(privateBinder); + } + + getExposedKeys(); // ensure exposedKeysToSources is populated + for (Map.Entry, Object> entry : exposedKeysToSources.entrySet()) { + privateBinder.withSource(entry.getValue()).expose(entry.getKey()); + } + } + + public Object getExposedSource(Key key) { + getExposedKeys(); // ensure exposedKeysToSources is populated + Object source = exposedKeysToSources.get(key); + checkArgument(source != null, "%s not exposed by %s.", key, this); + return source; + } + + @Override public String toString() { + return new ToStringBuilder(PrivateElements.class) + .add("exposedKeys", getExposedKeys()) + .add("source", getSource()) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderInstanceBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderInstanceBindingImpl.java new file mode 100644 index 00000000000..f0eb9b7d0d3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderInstanceBindingImpl.java @@ -0,0 +1,96 @@ +/* +Copyright (C) 2007 Google Inc. + +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. +*/ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.HasDependencies; +import org.elasticsearch.util.guice.inject.spi.InjectionPoint; +import org.elasticsearch.util.guice.inject.spi.ProviderInstanceBinding; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.util.Set; + +public final class ProviderInstanceBindingImpl extends BindingImpl + implements ProviderInstanceBinding { + + final Provider providerInstance; + final ImmutableSet injectionPoints; + + public ProviderInstanceBindingImpl(Injector injector, Key key, + Object source, InternalFactory internalFactory, Scoping scoping, + Provider providerInstance, + Set injectionPoints) { + super(injector, key, source, internalFactory, scoping); + this.providerInstance = providerInstance; + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + } + + public ProviderInstanceBindingImpl(Object source, Key key, Scoping scoping, + Set injectionPoints, Provider providerInstance) { + super(source, key, scoping); + this.injectionPoints = ImmutableSet.copyOf(injectionPoints); + this.providerInstance = providerInstance; + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public Provider getProviderInstance() { + return providerInstance; + } + + public Set getInjectionPoints() { + return injectionPoints; + } + + public Set> getDependencies() { + return providerInstance instanceof HasDependencies + ? ImmutableSet.copyOf(((HasDependencies) providerInstance).getDependencies()) + : Dependency.forInjectionPoints(injectionPoints); + } + + public BindingImpl withScoping(Scoping scoping) { + return new ProviderInstanceBindingImpl( + getSource(), getKey(), scoping, injectionPoints, providerInstance); + } + + public BindingImpl withKey(Key key) { + return new ProviderInstanceBindingImpl( + getSource(), key, getScoping(), injectionPoints, providerInstance); + } + + public void applyTo(Binder binder) { + getScoping().applyTo( + binder.withSource(getSource()).bind(getKey()).toProvider(getProviderInstance())); + } + + @Override + public String toString() { + return new ToStringBuilder(ProviderInstanceBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .add("scope", getScoping()) + .add("provider", providerInstance) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderMethod.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderMethod.java new file mode 100644 index 00000000000..b68c41ef2f8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderMethod.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Exposed; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.PrivateBinder; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.ProviderWithDependencies; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +/** + * A provider that invokes a method and returns its result. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public class ProviderMethod implements ProviderWithDependencies { + private final Key key; + private final Class scopeAnnotation; + private final Object instance; + private final Method method; + private final ImmutableSet> dependencies; + private final List> parameterProviders; + private final boolean exposed; + + /** + * @param method the method to invoke. It's return type must be the same type as {@code key}. + */ + ProviderMethod(Key key, Method method, Object instance, + ImmutableSet> dependencies, List> parameterProviders, + Class scopeAnnotation) { + this.key = key; + this.scopeAnnotation = scopeAnnotation; + this.instance = instance; + this.dependencies = dependencies; + this.method = method; + this.parameterProviders = parameterProviders; + this.exposed = method.isAnnotationPresent(Exposed.class); + + method.setAccessible(true); + } + + public Key getKey() { + return key; + } + + public Method getMethod() { + return method; + } + + // exposed for GIN + public Object getInstance() { + return instance; + } + + public void configure(Binder binder) { + binder = binder.withSource(method); + + if (scopeAnnotation != null) { + binder.bind(key).toProvider(this).in(scopeAnnotation); + } else { + binder.bind(key).toProvider(this); + } + + if (exposed) { + // the cast is safe 'cause the only binder we have implements PrivateBinder. If there's a + // misplaced @Exposed, calling this will add an error to the binder's error queue + ((PrivateBinder) binder).expose(key); + } + } + + public T get() { + Object[] parameters = new Object[parameterProviders.size()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = parameterProviders.get(i).get(); + } + + try { + // We know this cast is safe becase T is the method's return type. + @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" }) + T result = (T) method.invoke(instance, parameters); + return result; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public Set> getDependencies() { + return dependencies; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderMethodsModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderMethodsModule.java new file mode 100644 index 00000000000..94380b3835d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ProviderMethodsModule.java @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Module; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Provides; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.util.Modules; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.List; + +/** + * Creates bindings to methods annotated with {@literal @}{@link Provides}. Use the scope and + * binding annotations on the provider method to configure the binding. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + */ +public final class ProviderMethodsModule implements Module { + private final Object delegate; + private final TypeLiteral typeLiteral; + + private ProviderMethodsModule(Object delegate) { + this.delegate = checkNotNull(delegate, "delegate"); + this.typeLiteral = TypeLiteral.get(this.delegate.getClass()); + } + + /** + * Returns a module which creates bindings for provider methods from the given module. + */ + public static Module forModule(Module module) { + return forObject(module); + } + + /** + * Returns a module which creates bindings for provider methods from the given object. + * This is useful notably for GIN + */ + public static Module forObject(Object object) { + // avoid infinite recursion, since installing a module always installs itself + if (object instanceof ProviderMethodsModule) { + return Modules.EMPTY_MODULE; + } + + return new ProviderMethodsModule(object); + } + public synchronized void configure(Binder binder) { + for (ProviderMethod providerMethod : getProviderMethods(binder)) { + providerMethod.configure(binder); + } + } + + public List> getProviderMethods(Binder binder) { + List> result = Lists.newArrayList(); + for (Class c = delegate.getClass(); c != Object.class; c = c.getSuperclass()) { + for (Method method : c.getDeclaredMethods()) { + if (method.isAnnotationPresent(Provides.class)) { + result.add(createProviderMethod(binder, method)); + } + } + } + return result; + } + + ProviderMethod createProviderMethod(Binder binder, final Method method) { + binder = binder.withSource(method); + Errors errors = new Errors(method); + + // prepare the parameter providers + List> dependencies = Lists.newArrayList(); + List> parameterProviders = Lists.newArrayList(); + List> parameterTypes = typeLiteral.getParameterTypes(method); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + for (int i = 0; i < parameterTypes.size(); i++) { + Key key = getKey(errors, parameterTypes.get(i), method, parameterAnnotations[i]); + dependencies.add(Dependency.get(key)); + parameterProviders.add(binder.getProvider(key)); + } + + @SuppressWarnings("unchecked") // Define T as the method's return type. + TypeLiteral returnType = (TypeLiteral) typeLiteral.getReturnType(method); + + Key key = getKey(errors, returnType, method, method.getAnnotations()); + Class scopeAnnotation + = Annotations.findScopeAnnotation(errors, method.getAnnotations()); + + for (Message message : errors.getMessages()) { + binder.addError(message); + } + + return new ProviderMethod(key, method, delegate, ImmutableSet.copyOf(dependencies), + parameterProviders, scopeAnnotation); + } + + Key getKey(Errors errors, TypeLiteral type, Member member, Annotation[] annotations) { + Annotation bindingAnnotation = Annotations.findBindingAnnotation(errors, member, annotations); + return bindingAnnotation == null ? Key.get(type) : Key.get(type, bindingAnnotation); + } + + @Override public boolean equals(Object o) { + return o instanceof ProviderMethodsModule + && ((ProviderMethodsModule) o).delegate == delegate; + } + + @Override public int hashCode() { + return delegate.hashCode(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Scoping.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Scoping.java new file mode 100644 index 00000000000..51e17aae1c7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Scoping.java @@ -0,0 +1,209 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Scope; +import org.elasticsearch.util.guice.inject.Scopes; +import org.elasticsearch.util.guice.inject.Singleton; +import org.elasticsearch.util.guice.inject.Stage; +import org.elasticsearch.util.guice.inject.binder.ScopedBindingBuilder; +import org.elasticsearch.util.guice.inject.spi.BindingScopingVisitor; +import java.lang.annotation.Annotation; + +/** + * References a scope, either directly (as a scope instance), or indirectly (as a scope annotation). + * The scope's eager or laziness is also exposed. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public abstract class Scoping { + + /** + * No scoping annotation has been applied. Note that this is different from {@code + * in(Scopes.NO_SCOPE)}, where the 'NO_SCOPE' has been explicitly applied. + */ + public static final Scoping UNSCOPED = new Scoping() { + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitNoScoping(); + } + + @Override public Scope getScopeInstance() { + return Scopes.NO_SCOPE; + } + + @Override public String toString() { + return Scopes.NO_SCOPE.toString(); + } + + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + // do nothing + } + }; + + public static final Scoping SINGLETON_ANNOTATION = new Scoping() { + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScopeAnnotation(Singleton.class); + } + + @Override public Class getScopeAnnotation() { + return Singleton.class; + } + + @Override public String toString() { + return Singleton.class.getName(); + } + + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Singleton.class); + } + }; + + public static final Scoping SINGLETON_INSTANCE = new Scoping() { + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScope(Scopes.SINGLETON); + } + + @Override public Scope getScopeInstance() { + return Scopes.SINGLETON; + } + + @Override public String toString() { + return Scopes.SINGLETON.toString(); + } + + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(Scopes.SINGLETON); + } + }; + + public static final Scoping EAGER_SINGLETON = new Scoping() { + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitEagerSingleton(); + } + + @Override public Scope getScopeInstance() { + return Scopes.SINGLETON; + } + + @Override public String toString() { + return "eager singleton"; + } + + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.asEagerSingleton(); + } + }; + + public static Scoping forAnnotation(final Class scopingAnnotation) { + if (scopingAnnotation == Singleton.class) { + return SINGLETON_ANNOTATION; + } + + return new Scoping() { + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScopeAnnotation(scopingAnnotation); + } + + @Override public Class getScopeAnnotation() { + return scopingAnnotation; + } + + @Override public String toString() { + return scopingAnnotation.getName(); + } + + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(scopingAnnotation); + } + }; + } + + public static Scoping forInstance(final Scope scope) { + if (scope == Scopes.SINGLETON) { + return SINGLETON_INSTANCE; + } + + return new Scoping() { + public V acceptVisitor(BindingScopingVisitor visitor) { + return visitor.visitScope(scope); + } + + @Override public Scope getScopeInstance() { + return scope; + } + + @Override public String toString() { + return scope.toString(); + } + + public void applyTo(ScopedBindingBuilder scopedBindingBuilder) { + scopedBindingBuilder.in(scope); + } + }; + } + + /** + * Returns true if this scope was explicitly applied. If no scope was explicitly applied then the + * scoping annotation will be used. + */ + public boolean isExplicitlyScoped() { + return this != UNSCOPED; + } + + /** + * Returns true if this is the default scope. In this case a new instance will be provided for + * each injection. + */ + public boolean isNoScope() { + return getScopeInstance() == Scopes.NO_SCOPE; + } + + /** + * Returns true if this scope is a singleton that should be loaded eagerly in {@code stage}. + */ + public boolean isEagerSingleton(Stage stage) { + if (this == EAGER_SINGLETON) { + return true; + } + + if (stage == Stage.PRODUCTION) { + return this == SINGLETON_ANNOTATION || this == SINGLETON_INSTANCE; + } + + return false; + } + + /** + * Returns the scope instance, or {@code null} if that isn't known for this instance. + */ + public Scope getScopeInstance() { + return null; + } + + /** + * Returns the scope annotation, or {@code null} if that isn't known for this instance. + */ + public Class getScopeAnnotation() { + return null; + } + + public abstract V acceptVisitor(BindingScopingVisitor visitor); + + public abstract void applyTo(ScopedBindingBuilder scopedBindingBuilder); + + private Scoping() {} +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/SourceProvider.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/SourceProvider.java new file mode 100644 index 00000000000..d19f9bd1ef5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/SourceProvider.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import static org.elasticsearch.util.gcommon.collect.Iterables.concat; +import java.util.List; + +/** + * Provides access to the calling line of code. + * + * @author crazybob@google.com (Bob Lee) + */ +public class SourceProvider { + + /** Indicates that the source is unknown. */ + public static final Object UNKNOWN_SOURCE = "[unknown source]"; + + private final ImmutableSet classNamesToSkip; + + public SourceProvider() { + this.classNamesToSkip = ImmutableSet.of(SourceProvider.class.getName()); + } + + public static final SourceProvider DEFAULT_INSTANCE + = new SourceProvider(ImmutableSet.of(SourceProvider.class.getName())); + + private SourceProvider(Iterable classesToSkip) { + this.classNamesToSkip = ImmutableSet.copyOf(classesToSkip); + } + + /** Returns a new instance that also skips {@code moreClassesToSkip}. */ + public SourceProvider plusSkippedClasses(Class... moreClassesToSkip) { + return new SourceProvider(concat(classNamesToSkip, asStrings(moreClassesToSkip))); + } + + /** Returns the class names as Strings */ + private static List asStrings(Class... classes) { + List strings = Lists.newArrayList(); + for (Class c : classes) { + strings.add(c.getName()); + } + return strings; + } + + /** + * Returns the calling line of code. The selected line is the nearest to the top of the stack that + * is not skipped. + */ + public StackTraceElement get() { + for (final StackTraceElement element : new Throwable().getStackTrace()) { + String className = element.getClassName(); + if (!classNamesToSkip.contains(className)) { + return element; + } + } + throw new AssertionError(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/StackTraceElements.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/StackTraceElements.java new file mode 100644 index 00000000000..20cd2935451 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/StackTraceElements.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; + +/** + * Creates stack trace elements for members. + * + * @author crazybob@google.com (Bob Lee) + */ +public class StackTraceElements { + + public static Object forMember(Member member) { + if (member == null) { + return SourceProvider.UNKNOWN_SOURCE; + } + + Class declaringClass = member.getDeclaringClass(); + + String fileName = null; + int lineNumber = -1; + + Class memberType = MoreTypes.memberType(member); + String memberName = memberType == Constructor.class ? "" : member.getName(); + return new StackTraceElement(declaringClass.getName(), memberName, fileName, lineNumber); + } + + public static Object forType(Class implementation) { + String fileName = null; + int lineNumber = -1; + + return new StackTraceElement(implementation.getName(), "class", fileName, lineNumber); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Stopwatch.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Stopwatch.java new file mode 100644 index 00000000000..94114d321ca --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Stopwatch.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.util.logging.Logger; + +/** + * Enables simple performance monitoring. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Stopwatch { + private static final Logger logger = Logger.getLogger(Stopwatch.class.getName()); + + private long start = System.currentTimeMillis(); + + /** + * Resets and returns elapsed time in milliseconds. + */ + public long reset() { + long now = System.currentTimeMillis(); + try { + return now - start; + } finally { + start = now; + } + } + + /** + * Resets and logs elapsed time in milliseconds. + */ + public void resetAndLog(String label) { + logger.fine(label + ": " + reset() + "ms"); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Strings.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Strings.java new file mode 100644 index 00000000000..937058088d1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/Strings.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +/** + * String utilities. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Strings { + private Strings() {} + + /** + * Returns a string that is equivalent to the specified string with its + * first character converted to uppercase as by {@link String#toUpperCase}. + * The returned string will have the same value as the specified string if + * its first character is non-alphabetic, if its first character is already + * uppercase, or if the specified string is of length 0. + * + *

For example: + *

+   *    capitalize("foo bar").equals("Foo bar");
+   *    capitalize("2b or not 2b").equals("2b or not 2b")
+   *    capitalize("Foo bar").equals("Foo bar");
+   *    capitalize("").equals("");
+   * 
+ * + * @param s the string whose first character is to be uppercased + * @return a string equivalent to s with its first character + * converted to uppercase + * @throws NullPointerException if s is null + */ + public static String capitalize(String s) { + if (s.length() == 0) { + return s; + } + char first = s.charAt(0); + char capitalized = Character.toUpperCase(first); + return (first == capitalized) + ? s + : capitalized + s.substring(1); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ToStringBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ToStringBuilder.java new file mode 100644 index 00000000000..e489da378d8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/ToStringBuilder.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Helps with {@code toString()} methods. + * + * @author crazybob@google.com (Bob Lee) + */ +public class ToStringBuilder { + + // Linked hash map ensures ordering. + final Map map = new LinkedHashMap(); + + final String name; + + public ToStringBuilder(String name) { + this.name = name; + } + + public ToStringBuilder(Class type) { + this.name = type.getSimpleName(); + } + + public ToStringBuilder add(String name, Object value) { + if (map.put(name, value) != null) { + throw new RuntimeException("Duplicate names: " + name); + } + return this; + } + + @Override public String toString() { + return name + map.toString().replace('{', '[').replace('}', ']'); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UniqueAnnotations.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UniqueAnnotations.java new file mode 100644 index 00000000000..3bf17b32f1a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UniqueAnnotations.java @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.BindingAnnotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +public class UniqueAnnotations { + private UniqueAnnotations() {} + private static final AtomicInteger nextUniqueValue = new AtomicInteger(1); + + /** + * Returns an annotation instance that is not equal to any other annotation + * instances, for use in creating distinct {@link org.elasticsearch.util.guice.inject.Key}s. + */ + public static Annotation create() { + return create(nextUniqueValue.getAndIncrement()); + } + + static Annotation create(final int value) { + return new Internal() { + public int value() { + return value; + } + + public Class annotationType() { + return Internal.class; + } + + @Override public String toString() { + return "@" + Internal.class.getName() + "(value=" + value + ")"; + } + + @Override public boolean equals(Object o) { + return o instanceof Internal + && ((Internal) o).value() == value(); + } + + @Override public int hashCode() { + return (127 * "value".hashCode()) ^ value; + } + }; + } + + @Retention(RUNTIME) @BindingAnnotation + @interface Internal { + int value(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UnmodifiableIterator.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UnmodifiableIterator.java new file mode 100644 index 00000000000..12d1d15bd56 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UnmodifiableIterator.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import java.util.Iterator; + +/** + * An iterator that does not support {@link #remove}. + * + * @author Jared Levy + */ +public abstract class UnmodifiableIterator implements Iterator { + + /** + * Guaranteed to throw an exception and leave the underlying data unmodified. + * + * @throws UnsupportedOperationException always + */ + public final void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UntargettedBindingImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UntargettedBindingImpl.java new file mode 100644 index 00000000000..d83f997670c --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/UntargettedBindingImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.internal; + +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.spi.BindingTargetVisitor; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.UntargettedBinding; + +public class UntargettedBindingImpl extends BindingImpl implements UntargettedBinding { + + public UntargettedBindingImpl(Injector injector, Key key, Object source) { + super(injector, key, source, new InternalFactory() { + public T get(Errors errors, InternalContext context, Dependency dependency) { + throw new AssertionError(); + } + }, Scoping.UNSCOPED); + } + + public UntargettedBindingImpl(Object source, Key key, Scoping scoping) { + super(source, key, scoping); + } + + public V acceptTargetVisitor(BindingTargetVisitor visitor) { + return visitor.visit(this); + } + + public BindingImpl withScoping(Scoping scoping) { + return new UntargettedBindingImpl(getSource(), getKey(), scoping); + } + + public BindingImpl withKey(Key key) { + return new UntargettedBindingImpl(getSource(), key, getScoping()); + } + + public void applyTo(Binder binder) { + getScoping().applyTo(binder.withSource(getSource()).bind(getKey())); + } + + @Override public String toString() { + return new ToStringBuilder(UntargettedBinding.class) + .add("key", getKey()) + .add("source", getSource()) + .toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/package-info.java new file mode 100644 index 00000000000..c2082744c12 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/internal/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Guice (sounds like like "juice") + */ + +package org.elasticsearch.util.guice.inject.internal; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/AbstractMatcher.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/AbstractMatcher.java new file mode 100644 index 00000000000..5dc946deb47 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/AbstractMatcher.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.matcher; + +import java.io.Serializable; + +/** + * Implements {@code and()} and {@code or()}. + * + * @author crazybob@google.com (Bob Lee) + */ +public abstract class AbstractMatcher implements Matcher { + + public Matcher and(final Matcher other) { + return new AndMatcher(this, other); + } + + public Matcher or(Matcher other) { + return new OrMatcher(this, other); + } + + private static class AndMatcher extends AbstractMatcher implements Serializable { + private final Matcher a, b; + + public AndMatcher(Matcher a, Matcher b) { + this.a = a; + this.b = b; + } + + public boolean matches(T t) { + return a.matches(t) && b.matches(t); + } + + @Override public boolean equals(Object other) { + return other instanceof AndMatcher + && ((AndMatcher) other).a.equals(a) + && ((AndMatcher) other).b.equals(b); + } + + @Override public int hashCode() { + return 41 * (a.hashCode() ^ b.hashCode()); + } + + @Override public String toString() { + return "and(" + a + ", " + b + ")"; + } + + private static final long serialVersionUID = 0; + } + + private static class OrMatcher extends AbstractMatcher implements Serializable { + private final Matcher a, b; + + public OrMatcher(Matcher a, Matcher b) { + this.a = a; + this.b = b; + } + + public boolean matches(T t) { + return a.matches(t) || b.matches(t); + } + + @Override public boolean equals(Object other) { + return other instanceof OrMatcher + && ((OrMatcher) other).a.equals(a) + && ((OrMatcher) other).b.equals(b); + } + + @Override public int hashCode() { + return 37 * (a.hashCode() ^ b.hashCode()); + } + + @Override public String toString() { + return "or(" + a + ", " + b + ")"; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/Matcher.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/Matcher.java new file mode 100644 index 00000000000..021848eea6a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/Matcher.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.matcher; + +/** + * Returns {@code true} or {@code false} for a given input. + * + * @author crazybob@google.com (Bob Lee) + */ +public interface Matcher { + + /** + * Returns {@code true} if this matches {@code t}, {@code false} otherwise. + */ + boolean matches(T t); + + /** + * Returns a new matcher which returns {@code true} if both this and the + * given matcher return {@code true}. + */ + Matcher and(Matcher other); + + /** + * Returns a new matcher which returns {@code true} if either this or the + * given matcher return {@code true}. + */ + Matcher or(Matcher other); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/Matchers.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/Matchers.java new file mode 100644 index 00000000000..50c23445e65 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/Matchers.java @@ -0,0 +1,399 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.matcher; + +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; + +/** + * Matcher implementations. Supports matching classes and methods. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Matchers { + private Matchers() {} + + /** + * Returns a matcher which matches any input. + */ + public static Matcher any() { + return ANY; + } + + private static final Matcher ANY = new Any(); + + private static class Any extends AbstractMatcher implements Serializable { + public boolean matches(Object o) { + return true; + } + + @Override public String toString() { + return "any()"; + } + + public Object readResolve() { + return any(); + } + + private static final long serialVersionUID = 0; + } + + /** + * Inverts the given matcher. + */ + public static Matcher not(final Matcher p) { + return new Not(p); + } + + private static class Not extends AbstractMatcher implements Serializable { + final Matcher delegate; + + private Not(Matcher delegate) { + this.delegate = checkNotNull(delegate, "delegate"); + } + + public boolean matches(T t) { + return !delegate.matches(t); + } + + @Override public boolean equals(Object other) { + return other instanceof Not + && ((Not) other).delegate.equals(delegate); + } + + @Override public int hashCode() { + return -delegate.hashCode(); + } + + @Override public String toString() { + return "not(" + delegate + ")"; + } + + private static final long serialVersionUID = 0; + } + + private static void checkForRuntimeRetention( + Class annotationType) { + Retention retention = annotationType.getAnnotation(Retention.class); + checkArgument(retention != null && retention.value() == RetentionPolicy.RUNTIME, + "Annotation " + annotationType.getSimpleName() + " is missing RUNTIME retention"); + } + + /** + * Returns a matcher which matches elements (methods, classes, etc.) + * with a given annotation. + */ + public static Matcher annotatedWith( + final Class annotationType) { + return new AnnotatedWithType(annotationType); + } + + private static class AnnotatedWithType extends AbstractMatcher + implements Serializable { + private final Class annotationType; + + public AnnotatedWithType(Class annotationType) { + this.annotationType = checkNotNull(annotationType, "annotation type"); + checkForRuntimeRetention(annotationType); + } + + public boolean matches(AnnotatedElement element) { + return element.getAnnotation(annotationType) != null; + } + + @Override public boolean equals(Object other) { + return other instanceof AnnotatedWithType + && ((AnnotatedWithType) other).annotationType.equals(annotationType); + } + + @Override public int hashCode() { + return 37 * annotationType.hashCode(); + } + + @Override public String toString() { + return "annotatedWith(" + annotationType.getSimpleName() + ".class)"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches elements (methods, classes, etc.) + * with a given annotation. + */ + public static Matcher annotatedWith( + final Annotation annotation) { + return new AnnotatedWith(annotation); + } + + private static class AnnotatedWith extends AbstractMatcher + implements Serializable { + private final Annotation annotation; + + public AnnotatedWith(Annotation annotation) { + this.annotation = checkNotNull(annotation, "annotation"); + checkForRuntimeRetention(annotation.annotationType()); + } + + public boolean matches(AnnotatedElement element) { + Annotation fromElement = element.getAnnotation(annotation.annotationType()); + return fromElement != null && annotation.equals(fromElement); + } + + @Override public boolean equals(Object other) { + return other instanceof AnnotatedWith + && ((AnnotatedWith) other).annotation.equals(annotation); + } + + @Override public int hashCode() { + return 37 * annotation.hashCode(); + } + + @Override public String toString() { + return "annotatedWith(" + annotation + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches subclasses of the given type (as well as + * the given type). + */ + public static Matcher subclassesOf(final Class superclass) { + return new SubclassesOf(superclass); + } + + private static class SubclassesOf extends AbstractMatcher + implements Serializable { + private final Class superclass; + + public SubclassesOf(Class superclass) { + this.superclass = checkNotNull(superclass, "superclass"); + } + + public boolean matches(Class subclass) { + return superclass.isAssignableFrom(subclass); + } + + @Override public boolean equals(Object other) { + return other instanceof SubclassesOf + && ((SubclassesOf) other).superclass.equals(superclass); + } + + @Override public int hashCode() { + return 37 * superclass.hashCode(); + } + + @Override public String toString() { + return "subclassesOf(" + superclass.getSimpleName() + ".class)"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches objects equal to the given object. + */ + public static Matcher only(Object value) { + return new Only(value); + } + + private static class Only extends AbstractMatcher + implements Serializable { + private final Object value; + + public Only(Object value) { + this.value = checkNotNull(value, "value"); + } + + public boolean matches(Object other) { + return value.equals(other); + } + + @Override public boolean equals(Object other) { + return other instanceof Only + && ((Only) other).value.equals(value); + } + + @Override public int hashCode() { + return 37 * value.hashCode(); + } + + @Override public String toString() { + return "only(" + value + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches only the given object. + */ + public static Matcher identicalTo(final Object value) { + return new IdenticalTo(value); + } + + private static class IdenticalTo extends AbstractMatcher + implements Serializable { + private final Object value; + + public IdenticalTo(Object value) { + this.value = checkNotNull(value, "value"); + } + + public boolean matches(Object other) { + return value == other; + } + + @Override public boolean equals(Object other) { + return other instanceof IdenticalTo + && ((IdenticalTo) other).value == value; + } + + @Override public int hashCode() { + return 37 * System.identityHashCode(value); + } + + @Override public String toString() { + return "identicalTo(" + value + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches classes in the given package. Packages are specific to their + * classloader, so classes with the same package name may not have the same package at runtime. + */ + public static Matcher inPackage(final Package targetPackage) { + return new InPackage(targetPackage); + } + + private static class InPackage extends AbstractMatcher implements Serializable { + private final transient Package targetPackage; + private final String packageName; + + public InPackage(Package targetPackage) { + this.targetPackage = checkNotNull(targetPackage, "package"); + this.packageName = targetPackage.getName(); + } + + public boolean matches(Class c) { + return c.getPackage().equals(targetPackage); + } + + @Override public boolean equals(Object other) { + return other instanceof InPackage + && ((InPackage) other).targetPackage.equals(targetPackage); + } + + @Override public int hashCode() { + return 37 * targetPackage.hashCode(); + } + + @Override public String toString() { + return "inPackage(" + targetPackage.getName() + ")"; + } + + public Object readResolve() { + return inPackage(Package.getPackage(packageName)); + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches classes in the given package and its subpackages. Unlike + * {@link #inPackage(Package) inPackage()}, this matches classes from any classloader. + * + * @since 2.0 + */ + public static Matcher inSubpackage(final String targetPackageName) { + return new InSubpackage(targetPackageName); + } + + private static class InSubpackage extends AbstractMatcher implements Serializable { + private final String targetPackageName; + + public InSubpackage(String targetPackageName) { + this.targetPackageName = targetPackageName; + } + + public boolean matches(Class c) { + String classPackageName = c.getPackage().getName(); + return classPackageName.equals(targetPackageName) + || classPackageName.startsWith(targetPackageName + "."); + } + + @Override public boolean equals(Object other) { + return other instanceof InSubpackage + && ((InSubpackage) other).targetPackageName.equals(targetPackageName); + } + + @Override public int hashCode() { + return 37 * targetPackageName.hashCode(); + } + + @Override public String toString() { + return "inSubpackage(" + targetPackageName + ")"; + } + + private static final long serialVersionUID = 0; + } + + /** + * Returns a matcher which matches methods with matching return types. + */ + public static Matcher returns( + final Matcher> returnType) { + return new Returns(returnType); + } + + private static class Returns extends AbstractMatcher implements Serializable { + private final Matcher> returnType; + + public Returns(Matcher> returnType) { + this.returnType = checkNotNull(returnType, "return type matcher"); + } + + public boolean matches(Method m) { + return returnType.matches(m.getReturnType()); + } + + @Override public boolean equals(Object other) { + return other instanceof Returns + && ((Returns) other).returnType.equals(returnType); + } + + @Override public int hashCode() { + return 37 * returnType.hashCode(); + } + + @Override public String toString() { + return "returns(" + returnType + ")"; + } + + private static final long serialVersionUID = 0; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/package-info.java new file mode 100644 index 00000000000..5dfd78466a5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/matcher/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Used for matching things. Primarily used to pick out methods to which to + * apply interceptors. + */ +package org.elasticsearch.util.guice.inject.matcher; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/Element.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/Element.java new file mode 100644 index 00000000000..b0ebf9714fb --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/Element.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject.multibindings; + +import org.elasticsearch.util.guice.inject.BindingAnnotation; + +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * An internal binding annotation applied to each element in a multibinding. + * All elements are assigned a globally-unique id to allow different modules + * to contribute multibindings independently. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +@Retention(RUNTIME) @BindingAnnotation +@interface Element { + String setName(); + int uniqueId(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/MapBinder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/MapBinder.java new file mode 100644 index 00000000000..18ff1c7d574 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/MapBinder.java @@ -0,0 +1,372 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.multibindings; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Module; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder; +import org.elasticsearch.util.guice.inject.multibindings.Multibinder.RealMultibinder; +import static org.elasticsearch.util.guice.inject.multibindings.Multibinder.checkConfiguration; +import static org.elasticsearch.util.guice.inject.multibindings.Multibinder.checkNotNull; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.ProviderWithDependencies; +import org.elasticsearch.util.guice.inject.util.Types; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; + +import static org.elasticsearch.util.guice.inject.util.Types.newParameterizedType; +import static org.elasticsearch.util.guice.inject.util.Types.newParameterizedTypeWithOwner; +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * An API to bind multiple map entries separately, only to later inject them as + * a complete map. MapBinder is intended for use in your application's module: + *

+ * public class SnacksModule extends AbstractModule {
+ *   protected void configure() {
+ *     MapBinder<String, Snack> mapbinder
+ *         = MapBinder.newMapBinder(binder(), String.class, Snack.class);
+ *     mapbinder.addBinding("twix").toInstance(new Twix());
+ *     mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
+ *     mapbinder.addBinding("skittles").to(Skittles.class);
+ *   }
+ * }
+ * + *

With this binding, a {@link Map}{@code } can now be + * injected: + *


+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Map<String, Snack> snacks) { ... }
+ * }
+ * + *

In addition to binding {@code Map}, a mapbinder will also bind + * {@code Map>} for lazy value provision: + *


+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Map<String, Provider<Snack>> snackProviders) { ... }
+ * }
+ * + *

Creating mapbindings from different modules is supported. For example, it + * is okay to have both {@code CandyModule} and {@code ChipsModule} both + * create their own {@code MapBinder}, and to each contribute + * bindings to the snacks map. When that map is injected, it will contain + * entries from both modules. + * + *

Values are resolved at map injection time. If a value is bound to a + * provider, that provider's get method will be called each time the map is + * injected (unless the binding is also scoped, or a map of providers is injected). + * + *

Annotations are used to create different maps of the same key/value + * type. Each distinct annotation gets its own independent map. + * + *

Keys must be distinct. If the same key is bound more than + * once, map injection will fail. + * + *

Keys must be non-null. {@code addBinding(null)} will + * throw an unchecked exception. + * + *

Values must be non-null to use map injection. If any + * value is null, map injection will fail (although injecting a map of providers + * will not). + * + * @author dpb@google.com (David P. Baker) + */ +public abstract class MapBinder { + private MapBinder() {} + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a + * {@link Map} that is itself bound with no binding annotation. + */ + public static MapBinder newMapBinder(Binder binder, + TypeLiteral keyType, TypeLiteral valueType) { + binder = binder.skipSources(MapBinder.class, RealMapBinder.class); + return newMapBinder(binder, valueType, + Key.get(mapOf(keyType, valueType)), + Key.get(mapOfProviderOf(keyType, valueType)), + Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType))); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a + * {@link Map} that is itself bound with no binding annotation. + */ + public static MapBinder newMapBinder(Binder binder, + Class keyType, Class valueType) { + return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType)); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a + * {@link Map} that is itself bound with {@code annotation}. + */ + public static MapBinder newMapBinder(Binder binder, + TypeLiteral keyType, TypeLiteral valueType, Annotation annotation) { + binder = binder.skipSources(MapBinder.class, RealMapBinder.class); + return newMapBinder(binder, valueType, + Key.get(mapOf(keyType, valueType), annotation), + Key.get(mapOfProviderOf(keyType, valueType), annotation), + Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotation)); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a + * {@link Map} that is itself bound with {@code annotation}. + */ + public static MapBinder newMapBinder(Binder binder, + Class keyType, Class valueType, Annotation annotation) { + return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a + * {@link Map} that is itself bound with {@code annotationType}. + */ + public static MapBinder newMapBinder(Binder binder, TypeLiteral keyType, + TypeLiteral valueType, Class annotationType) { + binder = binder.skipSources(MapBinder.class, RealMapBinder.class); + return newMapBinder(binder, valueType, + Key.get(mapOf(keyType, valueType), annotationType), + Key.get(mapOfProviderOf(keyType, valueType), annotationType), + Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotationType)); + } + + /** + * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a + * {@link Map} that is itself bound with {@code annotationType}. + */ + public static MapBinder newMapBinder(Binder binder, Class keyType, + Class valueType, Class annotationType) { + return newMapBinder( + binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType); + } + + @SuppressWarnings("unchecked") // a map of is safely a Map + private static TypeLiteral> mapOf( + TypeLiteral keyType, TypeLiteral valueType) { + return (TypeLiteral>) TypeLiteral.get( + Types.mapOf(keyType.getType(), valueType.getType())); + } + + @SuppressWarnings("unchecked") // a provider map is safely a Map> + private static TypeLiteral>> mapOfProviderOf( + TypeLiteral keyType, TypeLiteral valueType) { + return (TypeLiteral>>) TypeLiteral.get( + Types.mapOf(keyType.getType(), newParameterizedType(Provider.class, valueType.getType()))); + } + + @SuppressWarnings("unchecked") // a provider entry is safely a Map.Entry> + private static TypeLiteral>> entryOfProviderOf( + TypeLiteral keyType, TypeLiteral valueType) { + return (TypeLiteral>>) TypeLiteral.get(newParameterizedTypeWithOwner( + Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType()))); + } + + private static MapBinder newMapBinder(Binder binder, TypeLiteral valueType, + Key> mapKey, Key>> providerMapKey, + Multibinder>> entrySetBinder) { + RealMapBinder mapBinder = new RealMapBinder( + binder, valueType, mapKey, providerMapKey, entrySetBinder); + binder.install(mapBinder); + return mapBinder; + } + + /** + * Returns a binding builder used to add a new entry in the map. Each + * key must be distinct (and non-null). Bound providers will be evaluated each + * time the map is injected. + * + *

It is an error to call this method without also calling one of the + * {@code to} methods on the returned binding builder. + * + *

Scoping elements independently is supported. Use the {@code in} method + * to specify a binding scope. + */ + public abstract LinkedBindingBuilder addBinding(K key); + + /** + * The actual mapbinder plays several roles: + * + *

As a MapBinder, it acts as a factory for LinkedBindingBuilders for + * each of the map's values. It delegates to a {@link Multibinder} of + * entries (keys to value providers). + * + *

As a Module, it installs the binding to the map itself, as well as to + * a corresponding map whose values are providers. It uses the entry set + * multibinder to construct the map and the provider map. + * + *

As a module, this implements equals() and hashcode() in order to trick + * Guice into executing its configure() method only once. That makes it so + * that multiple mapbinders can be created for the same target map, but + * only one is bound. Since the list of bindings is retrieved from the + * injector itself (and not the mapbinder), each mapbinder has access to + * all contributions from all equivalent mapbinders. + * + *

Rather than binding a single Map.Entry<K, V>, the map binder + * binds keys and values independently. This allows the values to be properly + * scoped. + * + *

We use a subclass to hide 'implements Module' from the public API. + */ + private static final class RealMapBinder extends MapBinder implements Module { + private final TypeLiteral valueType; + private final Key> mapKey; + private final Key>> providerMapKey; + private final RealMultibinder>> entrySetBinder; + + /* the target injector's binder. non-null until initialization, null afterwards */ + private Binder binder; + + private RealMapBinder(Binder binder, TypeLiteral valueType, + Key> mapKey, Key>> providerMapKey, + Multibinder>> entrySetBinder) { + this.valueType = valueType; + this.mapKey = mapKey; + this.providerMapKey = providerMapKey; + this.entrySetBinder = (RealMultibinder>>) entrySetBinder; + this.binder = binder; + } + + /** + * This creates two bindings. One for the {@code Map.Entry>} + * and another for {@code V}. + */ + @Override public LinkedBindingBuilder addBinding(K key) { + checkNotNull(key, "key"); + checkConfiguration(!isInitialized(), "MapBinder was already initialized"); + + Key valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName())); + entrySetBinder.addBinding().toInstance(new MapEntry>(key, + binder.getProvider(valueKey))); + return binder.bind(valueKey); + } + + public void configure(Binder binder) { + checkConfiguration(!isInitialized(), "MapBinder was already initialized"); + + final ImmutableSet> dependencies + = ImmutableSet.>of(Dependency.get(entrySetBinder.getSetKey())); + + // binds a Map> from a collection of Map> + final Provider>>> entrySetProvider = binder + .getProvider(entrySetBinder.getSetKey()); + binder.bind(providerMapKey).toProvider(new ProviderWithDependencies>>() { + private Map> providerMap; + + @SuppressWarnings("unused") + @Inject void initialize() { + RealMapBinder.this.binder = null; + + Map> providerMapMutable = new LinkedHashMap>(); + for (Entry> entry : entrySetProvider.get()) { + checkConfiguration(providerMapMutable.put(entry.getKey(), entry.getValue()) == null, + "Map injection failed due to duplicated key \"%s\"", entry.getKey()); + } + + providerMap = Collections.unmodifiableMap(providerMapMutable); + } + + public Map> get() { + return providerMap; + } + + public Set> getDependencies() { + return dependencies; + } + }); + + final Provider>> mapProvider = binder.getProvider(providerMapKey); + binder.bind(mapKey).toProvider(new ProviderWithDependencies>() { + public Map get() { + Map map = new LinkedHashMap(); + for (Entry> entry : mapProvider.get().entrySet()) { + V value = entry.getValue().get(); + K key = entry.getKey(); + checkConfiguration(value != null, + "Map injection failed due to null value for key \"%s\"", key); + map.put(key, value); + } + return Collections.unmodifiableMap(map); + } + + public Set> getDependencies() { + return dependencies; + } + }); + } + + private boolean isInitialized() { + return binder == null; + } + + @Override public boolean equals(Object o) { + return o instanceof RealMapBinder + && ((RealMapBinder) o).mapKey.equals(mapKey); + } + + @Override public int hashCode() { + return mapKey.hashCode(); + } + + private static final class MapEntry implements Map.Entry { + private final K key; + private final V value; + + private MapEntry(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(Object obj) { + return obj instanceof Map.Entry + && key.equals(((Map.Entry) obj).getKey()) + && value.equals(((Map.Entry) obj).getValue()); + } + + @Override public int hashCode() { + return 127 * ("key".hashCode() ^ key.hashCode()) + + 127 * ("value".hashCode() ^ value.hashCode()); + } + + @Override public String toString() { + return "MapEntry(" + key + ", " + value + ")"; + } + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/Multibinder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/Multibinder.java new file mode 100644 index 00000000000..9a14e517dc2 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/Multibinder.java @@ -0,0 +1,324 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.multibindings; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Module; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.binder.LinkedBindingBuilder; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.spi.Dependency; +import org.elasticsearch.util.guice.inject.spi.HasDependencies; +import org.elasticsearch.util.guice.inject.spi.Message; +import org.elasticsearch.util.guice.inject.util.Types; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * An API to bind multiple values separately, only to later inject them as a + * complete collection. Multibinder is intended for use in your application's + * module: + *


+ * public class SnacksModule extends AbstractModule {
+ *   protected void configure() {
+ *     Multibinder<Snack> multibinder
+ *         = Multibinder.newSetBinder(binder(), Snack.class);
+ *     multibinder.addBinding().toInstance(new Twix());
+ *     multibinder.addBinding().toProvider(SnickersProvider.class);
+ *     multibinder.addBinding().to(Skittles.class);
+ *   }
+ * }
+ * + *

With this binding, a {@link Set}{@code } can now be injected: + *


+ * class SnackMachine {
+ *   {@literal @}Inject
+ *   public SnackMachine(Set<Snack> snacks) { ... }
+ * }
+ * + *

Create multibindings from different modules is supported. For example, it + * is okay to have both {@code CandyModule} and {@code ChipsModule} to both + * create their own {@code Multibinder}, and to each contribute bindings + * to the set of snacks. When that set is injected, it will contain elements + * from both modules. + * + *

Elements are resolved at set injection time. If an element is bound to a + * provider, that provider's get method will be called each time the set is + * injected (unless the binding is also scoped). + * + *

Annotations are be used to create different sets of the same element + * type. Each distinct annotation gets its own independent collection of + * elements. + * + *

Elements must be distinct. If multiple bound elements + * have the same value, set injection will fail. + * + *

Elements must be non-null. If any set element is null, + * set injection will fail. + * + * @author jessewilson@google.com (Jesse Wilson) + */ +public abstract class Multibinder { + private Multibinder() {} + + /** + * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is + * itself bound with no binding annotation. + */ + public static Multibinder newSetBinder(Binder binder, TypeLiteral type) { + binder = binder.skipSources(RealMultibinder.class, Multibinder.class); + RealMultibinder result = new RealMultibinder(binder, type, "", + Key.get(Multibinder.setOf(type))); + binder.install(result); + return result; + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is + * itself bound with no binding annotation. + */ + public static Multibinder newSetBinder(Binder binder, Class type) { + return newSetBinder(binder, TypeLiteral.get(type)); + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is + * itself bound with {@code annotation}. + */ + public static Multibinder newSetBinder( + Binder binder, TypeLiteral type, Annotation annotation) { + binder = binder.skipSources(RealMultibinder.class, Multibinder.class); + RealMultibinder result = new RealMultibinder(binder, type, annotation.toString(), + Key.get(Multibinder.setOf(type), annotation)); + binder.install(result); + return result; + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is + * itself bound with {@code annotation}. + */ + public static Multibinder newSetBinder( + Binder binder, Class type, Annotation annotation) { + return newSetBinder(binder, TypeLiteral.get(type), annotation); + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is + * itself bound with {@code annotationType}. + */ + public static Multibinder newSetBinder(Binder binder, TypeLiteral type, + Class annotationType) { + binder = binder.skipSources(RealMultibinder.class, Multibinder.class); + RealMultibinder result = new RealMultibinder(binder, type, "@" + annotationType.getName(), + Key.get(Multibinder.setOf(type), annotationType)); + binder.install(result); + return result; + } + + /** + * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is + * itself bound with {@code annotationType}. + */ + public static Multibinder newSetBinder(Binder binder, Class type, + Class annotationType) { + return newSetBinder(binder, TypeLiteral.get(type), annotationType); + } + + @SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set + private static TypeLiteral> setOf(TypeLiteral elementType) { + Type type = Types.setOf(elementType.getType()); + return (TypeLiteral>) TypeLiteral.get(type); + } + + /** + * Returns a binding builder used to add a new element in the set. Each + * bound element must have a distinct value. Bound providers will be + * evaluated each time the set is injected. + * + *

It is an error to call this method without also calling one of the + * {@code to} methods on the returned binding builder. + * + *

Scoping elements independently is supported. Use the {@code in} method + * to specify a binding scope. + */ + public abstract LinkedBindingBuilder addBinding(); + + /** + * The actual multibinder plays several roles: + * + *

As a Multibinder, it acts as a factory for LinkedBindingBuilders for + * each of the set's elements. Each binding is given an annotation that + * identifies it as a part of this set. + * + *

As a Module, it installs the binding to the set itself. As a module, + * this implements equals() and hashcode() in order to trick Guice into + * executing its configure() method only once. That makes it so that + * multiple multibinders can be created for the same target collection, but + * only one is bound. Since the list of bindings is retrieved from the + * injector itself (and not the multibinder), each multibinder has access to + * all contributions from all multibinders. + * + *

As a Provider, this constructs the set instances. + * + *

We use a subclass to hide 'implements Module, Provider' from the public + * API. + */ + static final class RealMultibinder extends Multibinder + implements Module, Provider>, HasDependencies { + + private final TypeLiteral elementType; + private final String setName; + private final Key> setKey; + + /* the target injector's binder. non-null until initialization, null afterwards */ + private Binder binder; + + /* a provider for each element in the set. null until initialization, non-null afterwards */ + private List> providers; + private Set> dependencies; + + private RealMultibinder(Binder binder, TypeLiteral elementType, + String setName, Key> setKey) { + this.binder = checkNotNull(binder, "binder"); + this.elementType = checkNotNull(elementType, "elementType"); + this.setName = checkNotNull(setName, "setName"); + this.setKey = checkNotNull(setKey, "setKey"); + } + + @SuppressWarnings("unchecked") + public void configure(Binder binder) { + checkConfiguration(!isInitialized(), "Multibinder was already initialized"); + + binder.bind(setKey).toProvider(this); + } + + @Override public LinkedBindingBuilder addBinding() { + checkConfiguration(!isInitialized(), "Multibinder was already initialized"); + + return binder.bind(Key.get(elementType, new RealElement(setName))); + } + + /** + * Invoked by Guice at Injector-creation time to prepare providers for each + * element in this set. At this time the set's size is known, but its + * contents are only evaluated when get() is invoked. + */ + @Inject void initialize(Injector injector) { + providers = Lists.newArrayList(); + List> dependencies = Lists.newArrayList(); + for (Binding entry : injector.findBindingsByType(elementType)) { + + if (keyMatches(entry.getKey())) { + @SuppressWarnings("unchecked") // protected by findBindingsByType() + Binding binding = (Binding) entry; + providers.add(binding.getProvider()); + dependencies.add(Dependency.get(binding.getKey())); + } + } + + this.dependencies = ImmutableSet.copyOf(dependencies); + this.binder = null; + } + + private boolean keyMatches(Key key) { + return key.getTypeLiteral().equals(elementType) + && key.getAnnotation() instanceof Element + && ((Element) key.getAnnotation()).setName().equals(setName); + } + + private boolean isInitialized() { + return binder == null; + } + + public Set get() { + checkConfiguration(isInitialized(), "Multibinder is not initialized"); + + Set result = new LinkedHashSet(); + for (Provider provider : providers) { + final T newValue = provider.get(); + checkConfiguration(newValue != null, "Set injection failed due to null element"); + checkConfiguration(result.add(newValue), + "Set injection failed due to duplicated element \"%s\"", newValue); + } + return Collections.unmodifiableSet(result); + } + + String getSetName() { + return setName; + } + + Key> getSetKey() { + return setKey; + } + + public Set> getDependencies() { + return dependencies; + } + + @Override public boolean equals(Object o) { + return o instanceof RealMultibinder + && ((RealMultibinder) o).setKey.equals(setKey); + } + + @Override public int hashCode() { + return setKey.hashCode(); + } + + @Override public String toString() { + return new StringBuilder() + .append(setName) + .append(setName.length() > 0 ? " " : "") + .append("Multibinder<") + .append(elementType) + .append(">") + .toString(); + } + } + + static void checkConfiguration(boolean condition, String format, Object... args) { + if (condition) { + return; + } + + throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); + } + + static T checkNotNull(T reference, String name) { + if (reference != null) { + return reference; + } + + NullPointerException npe = new NullPointerException(name); + throw new ConfigurationException(ImmutableSet.of( + new Message(ImmutableList.of(), npe.toString(), npe))); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/RealElement.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/RealElement.java new file mode 100644 index 00000000000..2768ca7dc8d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/RealElement.java @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.multibindings; + +import java.lang.annotation.Annotation; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jessewilson@google.com (Jesse Wilson) + */ +class RealElement implements Element { + private static final AtomicInteger nextUniqueId = new AtomicInteger(1); + + private final int uniqueId; + private final String setName; + + RealElement(String setName) { + uniqueId = nextUniqueId.getAndIncrement(); + this.setName = setName; + } + + public String setName() { + return setName; + } + + public int uniqueId() { + return uniqueId; + } + + public Class annotationType() { + return Element.class; + } + + @Override public String toString() { + return "@" + Element.class.getName() + "(setName=" + setName + + ",uniqueId=" + uniqueId + ")"; + } + + @Override public boolean equals(Object o) { + return o instanceof Element + && ((Element) o).setName().equals(setName()) + && ((Element) o).uniqueId() == uniqueId(); + } + + @Override public int hashCode() { + return 127 * ("setName".hashCode() ^ setName.hashCode()) + + 127 * ("uniqueId".hashCode() ^ uniqueId); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/package-info.java new file mode 100644 index 00000000000..42d6aa3e8a3 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/multibindings/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +/** + * Extension for binding multiple instances in a collection; this extension requires {@code + * guice-multibindings-2.0.jar}. + */ +package org.elasticsearch.util.guice.inject.multibindings; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/Named.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/Named.java new file mode 100644 index 00000000000..4fbf7e71e25 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/Named.java @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.name; + +import org.elasticsearch.util.guice.inject.BindingAnnotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates named things. + * + * @author crazybob@google.com (Bob Lee) + */ +@Retention(RUNTIME) +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@BindingAnnotation +public @interface Named { + String value(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/NamedImpl.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/NamedImpl.java new file mode 100644 index 00000000000..adf4e2127b9 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/NamedImpl.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.name; + +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.io.Serializable; +import java.lang.annotation.Annotation; + +class NamedImpl implements Named, Serializable { + + private final String value; + + public NamedImpl(String value) { + this.value = checkNotNull(value, "name"); + } + + public String value() { + return this.value; + } + + public int hashCode() { + // This is specified in java.lang.Annotation. + return (127 * "value".hashCode()) ^ value.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof Named)) { + return false; + } + + Named other = (Named) o; + return value.equals(other.value()); + } + + public String toString() { + return "@" + Named.class.getName() + "(value=" + value + ")"; + } + + public Class annotationType() { + return Named.class; + } + + private static final long serialVersionUID = 0; +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/Names.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/Names.java new file mode 100644 index 00000000000..3ec4af25890 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/Names.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.name; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Key; +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; + +/** + * Utility methods for use with {@code @}{@link Named}. + * + * @author crazybob@google.com (Bob Lee) + */ +public class Names { + + private Names() {} + + /** + * Creates a {@link Named} annotation with {@code name} as the value. + */ + public static Named named(String name) { + return new NamedImpl(name); + } + + /** + * Creates a constant binding to {@code @Named(key)} for each entry in + * {@code properties}. + */ + public static void bindProperties(Binder binder, Map properties) { + binder = binder.skipSources(Names.class); + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + binder.bind(Key.get(String.class, new NamedImpl(key))).toInstance(value); + } + } + + /** + * Creates a constant binding to {@code @Named(key)} for each property. This + * method binds all properties including those inherited from + * {@link Properties#defaults defaults}. + */ + public static void bindProperties(Binder binder, Properties properties) { + binder = binder.skipSources(Names.class); + + // use enumeration to include the default properties + for (Enumeration e = properties.propertyNames(); e.hasMoreElements(); ) { + String propertyName = (String) e.nextElement(); + String value = properties.getProperty(propertyName); + binder.bind(Key.get(String.class, new NamedImpl(propertyName))).toInstance(value); + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/package-info.java new file mode 100644 index 00000000000..42e33737bcf --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/name/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Support for binding to string-based names. + */ +package org.elasticsearch.util.guice.inject.name; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/package-info.java new file mode 100644 index 00000000000..4b743911944 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/package-info.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Google Guice (pronounced "juice") is an ultra-lightweight dependency + * injection framework. Please refer to the Guice + * User's Guide + * for a gentle introduction. + * + *

The principal public APIs in this package are: + * + *

+ *
{@link org.elasticsearch.util.guice.inject.Inject} + *
The annotation you will use in your implementation classes to tell Guice + * where and how it should send in ("inject") the objects you depend on + * (your "dependencies"). + * + *
{@link org.elasticsearch.util.guice.inject.Module} + *
The interface you will implement in order to specify "bindings" -- + * instructions for how Guice should handle injection -- for a particular + * set of interfaces. + * + *
{@link org.elasticsearch.util.guice.inject.Binder} + *
The object that Guice passes into your {@link org.elasticsearch.util.guice.inject.Module} + * to collect these bindings. + * + *
{@link org.elasticsearch.util.guice.inject.Provider} + *
The interface you will implement when you need to customize exactly how + * Guice creates instances for a particular binding. + * + *
+ * + */ +package org.elasticsearch.util.guice.inject; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/BindingScopingVisitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/BindingScopingVisitor.java new file mode 100644 index 00000000000..6f0b51ae70b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/BindingScopingVisitor.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Scope; +import java.lang.annotation.Annotation; + +/** + * Visits each of the strategies used to scope an injection. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + * @since 2.0 + */ +public interface BindingScopingVisitor { + + /** + * Visit an eager singleton or single instance. This scope strategy is found on both module and + * injector bindings. + */ + V visitEagerSingleton(); + + /** + * Visit a scope instance. This scope strategy is found on both module and injector bindings. + */ + V visitScope(Scope scope); + + /** + * Visit a scope annotation. This scope strategy is found only on module bindings. The instance + * that implements this scope is registered by {@link org.elasticsearch.util.guice.inject.Binder#bindScope(Class, + * Scope) Binder.bindScope()}. + */ + V visitScopeAnnotation(Class scopeAnnotation); + + /** + * Visit an unspecified or unscoped strategy. On a module, this strategy indicates that the + * injector should use scoping annotations to find a scope. On an injector, it indicates that + * no scope is applied to the binding. An unscoped binding will behave like a scoped one when it + * is linked to a scoped binding. + */ + V visitNoScoping(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/BindingTargetVisitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/BindingTargetVisitor.java new file mode 100644 index 00000000000..e1d4ba9dfd6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/BindingTargetVisitor.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +/** + * Visits each of the strategies used to find an instance to satisfy an injection. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + * @since 2.0 + */ +public interface BindingTargetVisitor { + + /** + * Visit a instance binding. The same instance is returned for every injection. This target is + * found in both module and injector bindings. + */ + V visit(InstanceBinding binding); + + /** + * Visit a provider instance binding. The provider's {@code get} method is invoked to resolve + * injections. This target is found in both module and injector bindings. + */ + V visit(ProviderInstanceBinding binding); + + /** + * Visit a provider key binding. To resolve injections, the provider key is first resolved, then + * that provider's {@code get} method is invoked. This target is found in both module and injector + * bindings. + */ + V visit(ProviderKeyBinding binding); + + /** + * Visit a linked key binding. The other key's binding is used to resolve injections. This + * target is found in both module and injector bindings. + */ + V visit(LinkedKeyBinding binding); + + /** + * Visit a binding to a key exposed from an enclosed private environment. This target is only + * found in injector bindings. + */ + V visit(ExposedBinding binding); + + /** + * Visit an untargetted binding. This target is found only on module bindings. It indicates + * that the injector should use its implicit binding strategies to resolve injections. + */ + V visit(UntargettedBinding binding); + + /** + * Visit a constructor binding. To resolve injections, an instance is instantiated by invoking + * {@code constructor}. This target is found only on injector bindings. + */ + V visit(ConstructorBinding binding); + + /** + * Visit a binding created from converting a bound instance to a new type. The source binding + * has the same binding annotation but a different type. This target is found only on injector + * bindings. + */ + V visit(ConvertedConstantBinding binding); + + /** + * Visit a binding to a {@link org.elasticsearch.util.guice.inject.Provider} that delegates to the binding for the + * provided type. This target is found only on injector bindings. + */ + V visit(ProviderBinding binding); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ConstructorBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ConstructorBinding.java new file mode 100644 index 00000000000..13200bea709 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ConstructorBinding.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; + +import java.util.Set; + +/** + * A binding to the constructor of a concrete clss. To resolve injections, an instance is + * instantiated by invoking the constructor. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface ConstructorBinding extends Binding, HasDependencies { + + /** + * Gets the constructor this binding injects. + */ + InjectionPoint getConstructor(); + + /** + * Returns all instance method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + */ + Set getInjectableMembers(); +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ConvertedConstantBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ConvertedConstantBinding.java new file mode 100644 index 00000000000..97e299e9658 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ConvertedConstantBinding.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Key; +import java.util.Set; + +/** + * A binding created from converting a bound instance to a new type. The source binding has the same + * binding annotation but a different type. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface ConvertedConstantBinding extends Binding, HasDependencies { + + /** + * Returns the converted value. + */ + T getValue(); + + /** + * Returns the key for the source binding. That binding can e retrieved from an injector using + * {@link org.elasticsearch.util.guice.inject.Injector#getBinding(Key) Injector.getBinding(key)}. + */ + Key getSourceKey(); + + /** + * Returns a singleton set containing only the converted key. + */ + Set> getDependencies(); +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultBindingScopingVisitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultBindingScopingVisitor.java new file mode 100644 index 00000000000..d8cb9e0ed44 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultBindingScopingVisitor.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Scope; +import java.lang.annotation.Annotation; + +/** + * No-op visitor for subclassing. All interface methods simply delegate to + * {@link #visitOther()}, returning its result. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public class DefaultBindingScopingVisitor implements BindingScopingVisitor { + + /** + * Default visit implementation. Returns {@code null}. + */ + protected V visitOther() { + return null; + } + + public V visitEagerSingleton() { + return visitOther(); + } + + public V visitScope(Scope scope) { + return visitOther(); + } + + public V visitScopeAnnotation(Class scopeAnnotation) { + return visitOther(); + } + + public V visitNoScoping() { + return visitOther(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultBindingTargetVisitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultBindingTargetVisitor.java new file mode 100644 index 00000000000..8e0bf8edc16 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultBindingTargetVisitor.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; + +/** + * No-op visitor for subclassing. All interface methods simply delegate to {@link + * #visitOther(Binding)}, returning its result. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public abstract class DefaultBindingTargetVisitor implements BindingTargetVisitor { + + /** + * Default visit implementation. Returns {@code null}. + */ + protected V visitOther(Binding binding) { + return null; + } + + public V visit(InstanceBinding instanceBinding) { + return visitOther(instanceBinding); + } + + public V visit(ProviderInstanceBinding providerInstanceBinding) { + return visitOther(providerInstanceBinding); + } + + public V visit(ProviderKeyBinding providerKeyBinding) { + return visitOther(providerKeyBinding); + } + + public V visit(LinkedKeyBinding linkedKeyBinding) { + return visitOther(linkedKeyBinding); + } + + public V visit(ExposedBinding exposedBinding) { + return visitOther(exposedBinding); + } + + public V visit(UntargettedBinding untargettedBinding) { + return visitOther(untargettedBinding); + } + + public V visit(ConstructorBinding constructorBinding) { + return visitOther(constructorBinding); + } + + public V visit(ConvertedConstantBinding convertedConstantBinding) { + return visitOther(convertedConstantBinding); + } + + // javac says it's an error to cast ProviderBinding to Binding + @SuppressWarnings("unchecked") + public V visit(ProviderBinding providerBinding) { + return visitOther((Binding) providerBinding); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultElementVisitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultElementVisitor.java new file mode 100644 index 00000000000..57da9eb7d8a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/DefaultElementVisitor.java @@ -0,0 +1,79 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; + +/** + * No-op visitor for subclassing. All interface methods simply delegate to + * {@link #visitOther(Element)}, returning its result. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + * + * @author sberlin@gmail.com (Sam Berlin) + * @since 2.0 + */ +public abstract class DefaultElementVisitor implements ElementVisitor { + + /** + * Default visit implementation. Returns {@code null}. + */ + protected V visitOther(Element element) { + return null; + } + + public V visit(Message message) { + return visitOther(message); + } + + public V visit(Binding binding) { + return visitOther(binding); + } + + public V visit(ScopeBinding scopeBinding) { + return visitOther(scopeBinding); + } + + public V visit(TypeConverterBinding typeConverterBinding) { + return visitOther(typeConverterBinding); + } + + public V visit(ProviderLookup providerLookup) { + return visitOther(providerLookup); + } + + public V visit(InjectionRequest injectionRequest) { + return visitOther(injectionRequest); + } + + public V visit(StaticInjectionRequest staticInjectionRequest) { + return visitOther(staticInjectionRequest); + } + + public V visit(PrivateElements privateElements) { + return visitOther(privateElements); + } + + public V visit(MembersInjectorLookup lookup) { + return visitOther(lookup); + } + + public V visit(TypeListenerBinding binding) { + return visitOther(binding); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Dependency.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Dependency.java new file mode 100644 index 00000000000..685da8d2ac5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Dependency.java @@ -0,0 +1,127 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.gcommon.base.Objects; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.util.List; +import java.util.Set; + +/** + * A variable that can be resolved by an injector. + * + *

Use {@link #get} to build a freestanding dependency, or {@link InjectionPoint} to build one + * that's attached to a constructor, method or field. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class Dependency { + private final InjectionPoint injectionPoint; + private final Key key; + private final boolean nullable; + private final int parameterIndex; + + Dependency(InjectionPoint injectionPoint, Key key, + boolean nullable, int parameterIndex) { + this.injectionPoint = injectionPoint; + this.key = key; + this.nullable = nullable; + this.parameterIndex = parameterIndex; + } + + /** + * Returns a new dependency that is not attached to an injection point. The returned dependency is + * nullable. + */ + public static Dependency get(Key key) { + return new Dependency(null, key, true, -1); + } + + /** + * Returns the dependencies from the given injection points. + */ + public static Set> forInjectionPoints(Set injectionPoints) { + List> dependencies = Lists.newArrayList(); + for (InjectionPoint injectionPoint : injectionPoints) { + dependencies.addAll(injectionPoint.getDependencies()); + } + return ImmutableSet.copyOf(dependencies); + } + + /** + * Returns the key to the binding that satisfies this dependency. + */ + public Key getKey() { + return this.key; + } + + /** + * Returns true if null is a legal value for this dependency. + */ + public boolean isNullable() { + return nullable; + } + + /** + * Returns the injection point to which this dependency belongs, or null if this dependency isn't + * attached to a particular injection point. + */ + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + + /** + * Returns the index of this dependency in the injection point's parameter list, or {@code -1} if + * this dependency does not belong to a parameter list. Only method and constuctor dependencies + * are elements in a parameter list. + */ + public int getParameterIndex() { + return parameterIndex; + } + + @Override public int hashCode() { + return Objects.hashCode(injectionPoint, parameterIndex, key); + } + + @Override public boolean equals(Object o) { + if (o instanceof Dependency) { + Dependency dependency = (Dependency) o; + return Objects.equal(injectionPoint, dependency.injectionPoint) + && Objects.equal(parameterIndex, dependency.parameterIndex) + && Objects.equal(key, dependency.key); + } else { + return false; + } + } + + @Override public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(key); + if (injectionPoint != null) { + builder.append("@").append(injectionPoint); + if (parameterIndex != -1) { + builder.append("[").append(parameterIndex).append("]"); + } + } + return builder.toString(); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Element.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Element.java new file mode 100644 index 00000000000..384e126f1b1 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Element.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binder; + +/** + * A core component of a module or injector. + * + *

The elements of a module can be inspected, validated and rewritten. Use {@link + * Elements#getElements(org.elasticsearch.util.guice.inject.Module[]) Elements.getElements()} to read the elements + * from a module, and {@link Elements#getModule(Iterable) Elements.getModule()} to rewrite them. + * This can be used for static analysis and generation of Guice modules. + * + *

The elements of an injector can be inspected and exercised. Use {@link + * org.elasticsearch.util.guice.inject.Injector#getBindings Injector.getBindings()} to reflect on Guice injectors. + * + * @author jessewilson@google.com (Jesse Wilson) + * @author crazybob@google.com (Bob Lee) + * @since 2.0 + */ +public interface Element { + + /** + * Returns an arbitrary object containing information about the "place" where this element was + * configured. Used by Guice in the production of descriptive error messages. + * + *

Tools might specially handle types they know about; {@code StackTraceElement} is a good + * example. Tools should simply call {@code toString()} on the source object if the type is + * unfamiliar. + */ + Object getSource(); + + /** + * Accepts an element visitor. Invokes the visitor method specific to this element's type. + * + * @param visitor to call back on + */ + T acceptVisitor(ElementVisitor visitor); + + /** + * Writes this module element to the given binder (optional operation). + * + * @param binder to apply configuration element to + * @throws UnsupportedOperationException if the {@code applyTo} method is not supported by this + * element. + */ + void applyTo(Binder binder); + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ElementVisitor.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ElementVisitor.java new file mode 100644 index 00000000000..3b16b263df7 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ElementVisitor.java @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; + +/** + * Visit elements. + * + * @param any type to be returned by the visit method. Use {@link Void} with + * {@code return null} if no return type is needed. + * + * @since 2.0 + */ +public interface ElementVisitor { + + /** + * Visit a mapping from a key (type and optional annotation) to the strategy for getting + * instances of the type. + */ + V visit(Binding binding); + + /** + * Visit a registration of a scope annotation with the scope that implements it. + */ + V visit(ScopeBinding binding); + + /** + * Visit a registration of type converters for matching target types. + */ + V visit(TypeConverterBinding binding); + + /** + * Visit a request to inject the instance fields and methods of an instance. + */ + V visit(InjectionRequest request); + + /** + * Visit a request to inject the static fields and methods of type. + */ + V visit(StaticInjectionRequest request); + + /** + * Visit a lookup of the provider for a type. + */ + V visit(ProviderLookup lookup); + + /** + * Visit a lookup of the members injector. + */ + V visit(MembersInjectorLookup lookup); + + /** + * Visit an error message and the context in which it occured. + */ + V visit(Message message); + + /** + * Visit a collection of configuration elements for a {@linkplain org.elasticsearch.util.guice.inject.PrivateBinder + * private binder}. + */ + V visit(PrivateElements elements); + + /** + * Visit an injectable type listener binding. + */ + V visit(TypeListenerBinding binding); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Elements.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Elements.java new file mode 100644 index 00000000000..5ee3954a8c5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Elements.java @@ -0,0 +1,332 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.MembersInjector; +import org.elasticsearch.util.guice.inject.Module; +import org.elasticsearch.util.guice.inject.PrivateBinder; +import org.elasticsearch.util.guice.inject.PrivateModule; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Scope; +import org.elasticsearch.util.guice.inject.Stage; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.binder.AnnotatedBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.AnnotatedConstantBindingBuilder; +import org.elasticsearch.util.guice.inject.binder.AnnotatedElementBuilder; +import org.elasticsearch.util.guice.inject.internal.AbstractBindingBuilder; +import org.elasticsearch.util.guice.inject.internal.BindingBuilder; +import org.elasticsearch.util.guice.inject.internal.ConstantBindingBuilderImpl; +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkArgument; +import org.elasticsearch.util.guice.inject.internal.PrivateElementsImpl; +import org.elasticsearch.util.guice.inject.internal.ProviderMethodsModule; +import org.elasticsearch.util.guice.inject.internal.SourceProvider; +import org.elasticsearch.util.guice.inject.internal.ExposureBuilder; +import org.elasticsearch.util.guice.inject.matcher.Matcher; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Sets; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Exposes elements of a module so they can be inspected, validated or {@link + * Element#applyTo(Binder) rewritten}. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class Elements { + private static final BindingTargetVisitor GET_INSTANCE_VISITOR + = new DefaultBindingTargetVisitor() { + @Override public Object visit(InstanceBinding binding) { + return binding.getInstance(); + } + + @Override protected Object visitOther(Binding binding) { + throw new IllegalArgumentException(); + } + }; + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Module... modules) { + return getElements(Stage.DEVELOPMENT, Arrays.asList(modules)); + } + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Stage stage, Module... modules) { + return getElements(stage, Arrays.asList(modules)); + } + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Iterable modules) { + return getElements(Stage.DEVELOPMENT, modules); + } + + /** + * Records the elements executed by {@code modules}. + */ + public static List getElements(Stage stage, Iterable modules) { + RecordingBinder binder = new RecordingBinder(stage); + for (Module module : modules) { + binder.install(module); + } + return Collections.unmodifiableList(binder.elements); + } + + /** + * Returns the module composed of {@code elements}. + */ + public static Module getModule(final Iterable elements) { + return new Module() { + public void configure(Binder binder) { + for (Element element : elements) { + element.applyTo(binder); + } + } + }; + } + + @SuppressWarnings("unchecked") + static BindingTargetVisitor getInstanceVisitor() { + return (BindingTargetVisitor) GET_INSTANCE_VISITOR; + } + + private static class RecordingBinder implements Binder, PrivateBinder { + private final Stage stage; + private final Set modules; + private final List elements; + private final Object source; + private final SourceProvider sourceProvider; + + /** The binder where exposed bindings will be created */ + private final RecordingBinder parent; + private final PrivateElementsImpl privateElements; + + private RecordingBinder(Stage stage) { + this.stage = stage; + this.modules = Sets.newHashSet(); + this.elements = Lists.newArrayList(); + this.source = null; + this.sourceProvider = new SourceProvider().plusSkippedClasses( + Elements.class, RecordingBinder.class, AbstractModule.class, + ConstantBindingBuilderImpl.class, AbstractBindingBuilder.class, BindingBuilder.class); + this.parent = null; + this.privateElements = null; + } + + /** Creates a recording binder that's backed by {@code prototype}. */ + private RecordingBinder( + RecordingBinder prototype, Object source, SourceProvider sourceProvider) { + checkArgument(source == null ^ sourceProvider == null); + + this.stage = prototype.stage; + this.modules = prototype.modules; + this.elements = prototype.elements; + this.source = source; + this.sourceProvider = sourceProvider; + this.parent = prototype.parent; + this.privateElements = prototype.privateElements; + } + + /** Creates a private recording binder. */ + private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) { + this.stage = parent.stage; + this.modules = Sets.newHashSet(); + this.elements = privateElements.getElementsMutable(); + this.source = parent.source; + this.sourceProvider = parent.sourceProvider; + this.parent = parent; + this.privateElements = privateElements; + } + + public void bindScope(Class annotationType, Scope scope) { + elements.add(new ScopeBinding(getSource(), annotationType, scope)); + } + + @SuppressWarnings("unchecked") // it is safe to use the type literal for the raw type + public void requestInjection(Object instance) { + requestInjection((TypeLiteral) TypeLiteral.get(instance.getClass()), instance); + } + + public void requestInjection(TypeLiteral type, T instance) { + elements.add(new InjectionRequest(getSource(), type, instance)); + } + + public MembersInjector getMembersInjector(final TypeLiteral typeLiteral) { + final MembersInjectorLookup element + = new MembersInjectorLookup(getSource(), typeLiteral); + elements.add(element); + return element.getMembersInjector(); + } + + public MembersInjector getMembersInjector(Class type) { + return getMembersInjector(TypeLiteral.get(type)); + } + + public void bindListener(Matcher> typeMatcher, TypeListener listener) { + elements.add(new TypeListenerBinding(getSource(), listener, typeMatcher)); + } + + public void requestStaticInjection(Class... types) { + for (Class type : types) { + elements.add(new StaticInjectionRequest(getSource(), type)); + } + } + + public void install(Module module) { + if (modules.add(module)) { + Binder binder = this; + if (module instanceof PrivateModule) { + binder = binder.newPrivateBinder(); + } + + try { + module.configure(binder); + } catch (RuntimeException e) { + Collection messages = Errors.getMessagesFromThrowable(e); + if (!messages.isEmpty()) { + elements.addAll(messages); + } else { + addError(e); + } + } + binder.install(ProviderMethodsModule.forModule(module)); + } + } + + public Stage currentStage() { + return stage; + } + + public void addError(String message, Object... arguments) { + elements.add(new Message(getSource(), Errors.format(message, arguments))); + } + + public void addError(Throwable t) { + String message = "An exception was caught and reported. Message: " + t.getMessage(); + elements.add(new Message(ImmutableList.of(getSource()), message, t)); + } + + public void addError(Message message) { + elements.add(message); + } + + public AnnotatedBindingBuilder bind(Key key) { + return new BindingBuilder(this, elements, getSource(), key); + } + + public AnnotatedBindingBuilder bind(TypeLiteral typeLiteral) { + return bind(Key.get(typeLiteral)); + } + + public AnnotatedBindingBuilder bind(Class type) { + return bind(Key.get(type)); + } + + public AnnotatedConstantBindingBuilder bindConstant() { + return new ConstantBindingBuilderImpl(this, elements, getSource()); + } + + public Provider getProvider(final Key key) { + final ProviderLookup element = new ProviderLookup(getSource(), key); + elements.add(element); + return element.getProvider(); + } + + public Provider getProvider(Class type) { + return getProvider(Key.get(type)); + } + + public void convertToTypes(Matcher> typeMatcher, + TypeConverter converter) { + elements.add(new TypeConverterBinding(getSource(), typeMatcher, converter)); + } + + public RecordingBinder withSource(final Object source) { + return new RecordingBinder(this, source, null); + } + + public RecordingBinder skipSources(Class... classesToSkip) { + // if a source is specified explicitly, we don't need to skip sources + if (source != null) { + return this; + } + + SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip); + return new RecordingBinder(this, null, newSourceProvider); + } + + public PrivateBinder newPrivateBinder() { + PrivateElementsImpl privateElements = new PrivateElementsImpl(getSource()); + elements.add(privateElements); + return new RecordingBinder(this, privateElements); + } + + public void expose(Key key) { + exposeInternal(key); + } + + public AnnotatedElementBuilder expose(Class type) { + return exposeInternal(Key.get(type)); + } + + public AnnotatedElementBuilder expose(TypeLiteral type) { + return exposeInternal(Key.get(type)); + } + + private AnnotatedElementBuilder exposeInternal(Key key) { + if (privateElements == null) { + addError("Cannot expose %s on a standard binder. " + + "Exposed bindings are only applicable to private binders.", key); + return new AnnotatedElementBuilder() { + public void annotatedWith(Class annotationType) {} + public void annotatedWith(Annotation annotation) {} + }; + } + + ExposureBuilder builder = new ExposureBuilder(this, getSource(), key); + privateElements.addExposureBuilder(builder); + return builder; + } + + protected Object getSource() { + return sourceProvider != null + ? sourceProvider.get() + : source; + } + + @Override public String toString() { + return "Binder"; + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ExposedBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ExposedBinding.java new file mode 100644 index 00000000000..767de6d2f0b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ExposedBinding.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Binder; + +/** + * A binding to a key exposed from an enclosed private environment. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface ExposedBinding extends Binding, HasDependencies { + + /** + * Returns the enclosed environment that holds the original binding. + */ + PrivateElements getPrivateElements(); + + /** + * Unsupported. Always throws {@link UnsupportedOperationException}. + */ + void applyTo(Binder binder); +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/HasDependencies.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/HasDependencies.java new file mode 100644 index 00000000000..ebba01c38af --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/HasDependencies.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import java.util.Set; + +/** + * Implemented by {@link org.elasticsearch.util.guice.inject.Binding bindings}, {@link org.elasticsearch.util.guice.inject.Provider + * providers} and instances that expose their dependencies explicitly. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface HasDependencies { + + /** + * Returns the known dependencies for this type. If this has dependencies whose values are not + * known statically, a dependency for the {@link org.elasticsearch.util.guice.inject.Injector Injector} will be + * included in the returned set. + * + * @return a possibly empty set + */ + Set> getDependencies(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionListener.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionListener.java new file mode 100644 index 00000000000..a03074e3460 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionListener.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +/** + * Listens for injections into instances of type {@code I}. Useful for performing further + * injections, post-injection initialization, and more. + * + * @author crazybob@google.com (Bob Lee) + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface InjectionListener { + + /** + * Invoked by Guice after it injects the fields and methods of instance. + * + * @param injectee instance that Guice injected dependencies into + */ + void afterInjection(I injectee); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionPoint.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionPoint.java new file mode 100644 index 00000000000..8e8592e2a14 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionPoint.java @@ -0,0 +1,407 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.internal.Annotations; +import org.elasticsearch.util.guice.inject.internal.Errors; +import org.elasticsearch.util.guice.inject.internal.ErrorsException; +import org.elasticsearch.util.guice.inject.internal.MoreTypes; +import static org.elasticsearch.util.guice.inject.internal.MoreTypes.getRawType; +import org.elasticsearch.util.guice.inject.internal.Nullability; +import org.elasticsearch.util.gcommon.collect.ImmutableList; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * A constructor, field or method that can receive injections. Typically this is a member with the + * {@literal @}{@link Inject} annotation. For non-private, no argument constructors, the member may + * omit the annotation. + * + * @author crazybob@google.com (Bob Lee) + * @since 2.0 + */ +public final class InjectionPoint { + + private final boolean optional; + private final Member member; + private final ImmutableList> dependencies; + + private InjectionPoint(Member member, + ImmutableList> dependencies, boolean optional) { + this.member = member; + this.dependencies = dependencies; + this.optional = optional; + } + + InjectionPoint(TypeLiteral type, Method method) { + this.member = method; + + Inject inject = method.getAnnotation(Inject.class); + this.optional = inject.optional(); + + this.dependencies = forMember(method, type, method.getParameterAnnotations()); + } + + InjectionPoint(TypeLiteral type, Constructor constructor) { + this.member = constructor; + this.optional = false; + this.dependencies = forMember(constructor, type, constructor.getParameterAnnotations()); + } + + InjectionPoint(TypeLiteral type, Field field) { + this.member = field; + + Inject inject = field.getAnnotation(Inject.class); + this.optional = inject.optional(); + + Annotation[] annotations = field.getAnnotations(); + + Errors errors = new Errors(field); + Key key = null; + try { + key = Annotations.getKey(type.getFieldType(field), field, annotations, errors); + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + errors.throwConfigurationExceptionIfErrorsExist(); + + this.dependencies = ImmutableList.>of( + newDependency(key, Nullability.allowsNull(annotations), -1)); + } + + private ImmutableList> forMember(Member member, TypeLiteral type, + Annotation[][] paramterAnnotations) { + Errors errors = new Errors(member); + Iterator annotationsIterator = Arrays.asList(paramterAnnotations).iterator(); + + List> dependencies = Lists.newArrayList(); + int index = 0; + + for (TypeLiteral parameterType : type.getParameterTypes(member)) { + try { + Annotation[] parameterAnnotations = annotationsIterator.next(); + Key key = Annotations.getKey(parameterType, member, parameterAnnotations, errors); + dependencies.add(newDependency(key, Nullability.allowsNull(parameterAnnotations), index)); + index++; + } catch (ErrorsException e) { + errors.merge(e.getErrors()); + } + } + + errors.throwConfigurationExceptionIfErrorsExist(); + return ImmutableList.copyOf(dependencies); + } + + // This metohd is necessary to create a Dependency with proper generic type information + private Dependency newDependency(Key key, boolean allowsNull, int parameterIndex) { + return new Dependency(this, key, allowsNull, parameterIndex); + } + + /** + * Returns the injected constructor, field, or method. + */ + public Member getMember() { + return member; + } + + /** + * Returns the dependencies for this injection point. If the injection point is for a method or + * constructor, the dependencies will correspond to that member's parameters. Field injection + * points always have a single dependency for the field itself. + * + * @return a possibly-empty list + */ + public List> getDependencies() { + return dependencies; + } + + /** + * Returns true if this injection point shall be skipped if the injector cannot resolve bindings + * for all required dependencies. Both explicit bindings (as specified in a module), and implicit + * bindings ({@literal @}{@link org.elasticsearch.util.guice.inject.ImplementedBy ImplementedBy}, default + * constructors etc.) may be used to satisfy optional injection points. + */ + public boolean isOptional() { + return optional; + } + + @Override public boolean equals(Object o) { + return o instanceof InjectionPoint + && member.equals(((InjectionPoint) o).member); + } + + @Override public int hashCode() { + return member.hashCode(); + } + + @Override public String toString() { + return MoreTypes.toString(member); + } + + /** + * Returns a new injection point for the injectable constructor of {@code type}. + * + * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject}, + * or a no-arguments constructor that is not private. + * @throws ConfigurationException if there is no injectable constructor, more than one injectable + * constructor, or if parameters of the injectable constructor are malformed, such as a + * parameter with multiple binding annotations. + */ + public static InjectionPoint forConstructorOf(TypeLiteral type) { + Class rawType = getRawType(type.getType()); + Errors errors = new Errors(rawType); + + Constructor injectableConstructor = null; + for (Constructor constructor : rawType.getDeclaredConstructors()) { + Inject inject = constructor.getAnnotation(Inject.class); + if (inject != null) { + if (inject.optional()) { + errors.optionalConstructor(constructor); + } + + if (injectableConstructor != null) { + errors.tooManyConstructors(rawType); + } + + injectableConstructor = constructor; + checkForMisplacedBindingAnnotations(injectableConstructor, errors); + } + } + + errors.throwConfigurationExceptionIfErrorsExist(); + + if (injectableConstructor != null) { + return new InjectionPoint(type, injectableConstructor); + } + + // If no annotated constructor is found, look for a no-arg constructor instead. + try { + Constructor noArgConstructor = rawType.getDeclaredConstructor(); + + // Disallow private constructors on non-private classes (unless they have @Inject) + if (Modifier.isPrivate(noArgConstructor.getModifiers()) + && !Modifier.isPrivate(rawType.getModifiers())) { + errors.missingConstructor(rawType); + throw new ConfigurationException(errors.getMessages()); + } + + checkForMisplacedBindingAnnotations(noArgConstructor, errors); + return new InjectionPoint(type, noArgConstructor); + } catch (NoSuchMethodException e) { + errors.missingConstructor(rawType); + throw new ConfigurationException(errors.getMessages()); + } + } + + /** + * Returns a new injection point for the injectable constructor of {@code type}. + * + * @param type a concrete type with exactly one constructor annotated {@literal @}{@link Inject}, + * or a no-arguments constructor that is not private. + * @throws ConfigurationException if there is no injectable constructor, more than one injectable + * constructor, or if parameters of the injectable constructor are malformed, such as a + * parameter with multiple binding annotations. + */ + public static InjectionPoint forConstructorOf(Class type) { + return forConstructorOf(TypeLiteral.get(type)); + } + + /** + * Returns all static method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} + * of the valid injection points. + */ + public static Set forStaticMethodsAndFields(TypeLiteral type) { + List sink = Lists.newArrayList(); + Errors errors = new Errors(); + + addInjectionPoints(type, Factory.FIELDS, true, sink, errors); + addInjectionPoints(type, Factory.METHODS, true, sink, errors); + + ImmutableSet result = ImmutableSet.copyOf(sink); + if (errors.hasErrors()) { + throw new ConfigurationException(errors.getMessages()).withPartialValue(result); + } + return result; + } + + /** + * Returns all static method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} + * of the valid injection points. + */ + public static Set forStaticMethodsAndFields(Class type) { + return forStaticMethodsAndFields(TypeLiteral.get(type)); + } + + /** + * Returns all instance method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} + * of the valid injection points. + */ + public static Set forInstanceMethodsAndFields(TypeLiteral type) { + List sink = Lists.newArrayList(); + Errors errors = new Errors(); + + // TODO (crazybob): Filter out overridden members. + addInjectionPoints(type, Factory.FIELDS, false, sink, errors); + addInjectionPoints(type, Factory.METHODS, false, sink, errors); + + ImmutableSet result = ImmutableSet.copyOf(sink); + if (errors.hasErrors()) { + throw new ConfigurationException(errors.getMessages()).withPartialValue(result); + } + return result; + } + + /** + * Returns all instance method and field injection points on {@code type}. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} + * of the valid injection points. + */ + public static Set forInstanceMethodsAndFields(Class type) { + return forInstanceMethodsAndFields(TypeLiteral.get(type)); + } + + private static void checkForMisplacedBindingAnnotations(Member member, Errors errors) { + Annotation misplacedBindingAnnotation = Annotations.findBindingAnnotation( + errors, member, ((AnnotatedElement) member).getAnnotations()); + if (misplacedBindingAnnotation == null) { + return; + } + + // don't warn about misplaced binding annotations on methods when there's a field with the same + // name. In Scala, fields always get accessor methods (that we need to ignore). See bug 242. + if (member instanceof Method) { + try { + if (member.getDeclaringClass().getDeclaredField(member.getName()) != null) { + return; + } + } catch (NoSuchFieldException ignore) { + } + } + + errors.misplacedBindingAnnotation(member, misplacedBindingAnnotation); + } + + private static void addInjectionPoints(TypeLiteral type, + Factory factory, boolean statics, Collection injectionPoints, + Errors errors) { + if (type.getType() == Object.class) { + return; + } + + // Add injectors for superclass first. + TypeLiteral superType = type.getSupertype(type.getRawType().getSuperclass()); + addInjectionPoints(superType, factory, statics, injectionPoints, errors); + + // Add injectors for all members next + addInjectorsForMembers(type, factory, statics, injectionPoints, errors); + } + + private static void addInjectorsForMembers( + TypeLiteral typeLiteral, Factory factory, boolean statics, + Collection injectionPoints, Errors errors) { + for (M member : factory.getMembers(getRawType(typeLiteral.getType()))) { + if (isStatic(member) != statics) { + continue; + } + + Inject inject = member.getAnnotation(Inject.class); + if (inject == null) { + continue; + } + + try { + injectionPoints.add(factory.create(typeLiteral, member, errors)); + } catch (ConfigurationException ignorable) { + if (!inject.optional()) { + errors.merge(ignorable.getErrorMessages()); + } + } + } + } + + private static boolean isStatic(Member member) { + return Modifier.isStatic(member.getModifiers()); + } + + private interface Factory { + Factory FIELDS = new Factory() { + public Field[] getMembers(Class type) { + return type.getDeclaredFields(); + } + public InjectionPoint create(TypeLiteral typeLiteral, Field member, Errors errors) { + return new InjectionPoint(typeLiteral, member); + } + }; + + Factory METHODS = new Factory() { + public Method[] getMembers(Class type) { + return type.getDeclaredMethods(); + } + public InjectionPoint create(TypeLiteral typeLiteral, Method member, Errors errors) { + checkForMisplacedBindingAnnotations(member, errors); + return new InjectionPoint(typeLiteral, member); + } + }; + + M[] getMembers(Class type); + InjectionPoint create(TypeLiteral typeLiteral, M member, Errors errors); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionRequest.java new file mode 100644 index 00000000000..0f3c83b494d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InjectionRequest.java @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.util.Set; + +/** + * A request to inject the instance fields and methods of an instance. Requests are created + * explicitly in a module using {@link org.elasticsearch.util.guice.inject.Binder#requestInjection(Object) + * requestInjection()} statements: + *

+ *     requestInjection(serviceInstance);
+ * + * @author mikeward@google.com (Mike Ward) + * @since 2.0 + */ +public final class InjectionRequest implements Element { + + private final Object source; + private final TypeLiteral type; + private final T instance; + + public InjectionRequest(Object source, TypeLiteral type, T instance) { + this.source = checkNotNull(source, "source"); + this.type = checkNotNull(type, "type"); + this.instance = checkNotNull(instance, "instance"); + } + + public Object getSource() { + return source; + } + + public T getInstance() { + return instance; + } + + public TypeLiteral getType() { + return type; + } + + /** + * Returns the instance methods and fields of {@code instance} that will be injected to fulfill + * this request. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on the class of {@code + * instance}, such as a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} + * of the valid injection points. + */ + public Set getInjectionPoints() throws ConfigurationException { + return InjectionPoint.forInstanceMethodsAndFields(instance.getClass()); + } + + public R acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requestInjection(type, instance); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InstanceBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InstanceBinding.java new file mode 100644 index 00000000000..d1348b7c050 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/InstanceBinding.java @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import java.util.Set; + +/** + * A binding to a single instance. The same instance is returned for every injection. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface InstanceBinding extends Binding, HasDependencies { + + /** + * Returns the user-supplied instance. + */ + T getInstance(); + + /** + * Returns the field and method injection points of the instance, injected at injector-creation + * time only. + * + * @return a possibly empty set + */ + Set getInjectionPoints(); + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/LinkedKeyBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/LinkedKeyBinding.java new file mode 100644 index 00000000000..68f16b9382d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/LinkedKeyBinding.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Key; + +/** + * A binding to a linked key. The other key's binding is used to resolve injections. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface LinkedKeyBinding extends Binding { + + /** + * Returns the linked key used to resolve injections. That binding can be retrieved from an + * injector using {@link org.elasticsearch.util.guice.inject.Injector#getBinding(Key) Injector.getBinding(key)}. + */ + Key getLinkedKey(); + +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/MembersInjectorLookup.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/MembersInjectorLookup.java new file mode 100644 index 00000000000..a46d107b714 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/MembersInjectorLookup.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.MembersInjector; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; + +/** + * A lookup of the members injector for a type. Lookups are created explicitly in a module using + * {@link org.elasticsearch.util.guice.inject.Binder#getMembersInjector(Class) getMembersInjector()} statements: + *
+ *     MembersInjector<PaymentService> membersInjector
+ *         = getMembersInjector(PaymentService.class);
+ * + * @author crazybob@google.com (Bob Lee) + * @since 2.0 + */ +public final class MembersInjectorLookup implements Element { + + private final Object source; + private final TypeLiteral type; + private MembersInjector delegate; + + public MembersInjectorLookup(Object source, TypeLiteral type) { + this.source = checkNotNull(source, "source"); + this.type = checkNotNull(type, "type"); + } + + public Object getSource() { + return source; + } + + /** + * Gets the type containing the members to be injected. + */ + public TypeLiteral getType() { + return type; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + /** + * Sets the actual members injector. + * + * @throws IllegalStateException if the delegate is already set + */ + public void initializeDelegate(MembersInjector delegate) { + checkState(this.delegate == null, "delegate already initialized"); + this.delegate = checkNotNull(delegate, "delegate"); + } + + public void applyTo(Binder binder) { + initializeDelegate(binder.withSource(getSource()).getMembersInjector(type)); + } + + /** + * Returns the delegate members injector, or {@code null} if it has not yet been initialized. + * The delegate will be initialized when this element is processed, or otherwise used to create + * an injector. + */ + public MembersInjector getDelegate() { + return delegate; + } + + /** + * Returns the looked up members injector. The result is not valid until this lookup has been + * initialized, which usually happens when the injector is created. The members injector will + * throw an {@code IllegalStateException} if you try to use it beforehand. + */ + public MembersInjector getMembersInjector() { + return new MembersInjector() { + public void injectMembers(T instance) { + checkState(delegate != null, + "This MembersInjector cannot be used until the Injector has been created."); + delegate.injectMembers(instance); + } + + @Override public String toString() { + return "MembersInjector<" + type + ">"; + } + }; + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Message.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Message.java new file mode 100644 index 00000000000..e438d9bef13 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/Message.java @@ -0,0 +1,132 @@ +/** + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.internal.Errors; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.internal.SourceProvider; +import org.elasticsearch.util.gcommon.base.Objects; +import org.elasticsearch.util.gcommon.collect.ImmutableList; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.List; + +/** + * An error message and the context in which it occured. Messages are usually created internally by + * Guice and its extensions. Messages can be created explicitly in a module using {@link + * org.elasticsearch.util.guice.inject.Binder#addError(Throwable) addError()} statements: + *
+ *     try {
+ *       bindPropertiesFromFile();
+ *     } catch (IOException e) {
+ *       addError(e);
+ *     }
+ * + * @author crazybob@google.com (Bob Lee) + */ +public final class Message implements Serializable, Element { + private final String message; + private final Throwable cause; + private final List sources; + + /** + * @since 2.0 + */ + public Message(List sources, String message, Throwable cause) { + this.sources = ImmutableList.copyOf(sources); + this.message = checkNotNull(message, "message"); + this.cause = cause; + } + + public Message(Object source, String message) { + this(ImmutableList.of(source), message, null); + } + + public Message(String message) { + this(ImmutableList.of(), message, null); + } + + public String getSource() { + return sources.isEmpty() + ? SourceProvider.UNKNOWN_SOURCE.toString() + : Errors.convert(sources.get(sources.size() - 1)).toString(); + } + + /** @since 2.0 */ + public List getSources() { + return sources; + } + + /** + * Gets the error message text. + */ + public String getMessage() { + return message; + } + + /** @since 2.0 */ + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + /** + * Returns the throwable that caused this message, or {@code null} if this + * message was not caused by a throwable. + * + * @since 2.0 + */ + public Throwable getCause() { + return cause; + } + + @Override public String toString() { + return message; + } + + @Override public int hashCode() { + return message.hashCode(); + } + + @Override public boolean equals(Object o) { + if (!(o instanceof Message)) { + return false; + } + Message e = (Message) o; + return message.equals(e.message) && Objects.equal(cause, e.cause) && sources.equals(e.sources); + } + + /** @since 2.0 */ + public void applyTo(Binder binder) { + binder.withSource(getSource()).addError(this); + } + + /** + * When serialized, we eagerly convert sources to strings. This hurts our formatting, but it + * guarantees that the receiving end will be able to read the message. + */ + private Object writeReplace() throws ObjectStreamException { + Object[] sourcesAsStrings = sources.toArray(); + for (int i = 0; i < sourcesAsStrings.length; i++) { + sourcesAsStrings[i] = Errors.convert(sourcesAsStrings[i]).toString(); + } + return new Message(ImmutableList.of(sourcesAsStrings), message, cause); + } + + private static final long serialVersionUID = 0; +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/PrivateElements.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/PrivateElements.java new file mode 100644 index 00000000000..a651e64e00b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/PrivateElements.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.Key; +import java.util.List; +import java.util.Set; + +/** + * A private collection of elements that are hidden from the enclosing injector or module by + * default. See {@link org.elasticsearch.util.guice.inject.PrivateModule PrivateModule} for details. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface PrivateElements extends Element { + + /** + * Returns the configuration information in this private environment. + */ + List getElements(); + + /** + * Returns the child injector that hosts these private elements, or null if the elements haven't + * been used to create an injector. + */ + Injector getInjector(); + + /** + * Returns the unique exposed keys for these private elements. + */ + Set> getExposedKeys(); + + /** + * Returns an arbitrary object containing information about the "place" where this key was + * exposed. Used by Guice in the production of descriptive error messages. + * + *

Tools might specially handle types they know about; {@code StackTraceElement} is a good + * example. Tools should simply call {@code toString()} on the source object if the type is + * unfamiliar. + * + * @param key one of the keys exposed by this module. + */ + Object getExposedSource(Key key); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderBinding.java new file mode 100644 index 00000000000..8e6ed718237 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderBinding.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; + +/** + * A binding to a {@link Provider} that delegates to the binding for the provided type. This binding + * is used whenever a {@code Provider} is injected (as opposed to injecting {@code T} directly). + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface ProviderBinding> extends Binding { + + /** + * Returns the key whose binding is used to {@link Provider#get provide instances}. That binding + * can be retrieved from an injector using {@link org.elasticsearch.util.guice.inject.Injector#getBinding(Key) + * Injector.getBinding(providedKey)} + */ + Key getProvidedKey(); +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderInstanceBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderInstanceBinding.java new file mode 100644 index 00000000000..020354570df --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderInstanceBinding.java @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Provider; +import java.util.Set; + +/** + * A binding to a provider instance. The provider's {@code get} method is invoked to resolve + * injections. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface ProviderInstanceBinding extends Binding, HasDependencies { + + /** + * Returns the user-supplied, unscoped provider. + */ + Provider getProviderInstance(); + + /** + * Returns the field and method injection points of the provider, injected at injector-creation + * time only. + * + * @return a possibly empty set + */ + Set getInjectionPoints(); + +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderKeyBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderKeyBinding.java new file mode 100644 index 00000000000..6444ea3c450 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderKeyBinding.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; + +/** + * A binding to a provider key. To resolve injections, the provider key is first resolved, then that + * provider's {@code get} method is invoked. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface ProviderKeyBinding extends Binding { + + /** + * Returns the key used to resolve the provider's binding. That binding can be retrieved from an + * injector using {@link org.elasticsearch.util.guice.inject.Injector#getBinding(Key) + * Injector.getBinding(providerKey)} + */ + Key> getProviderKey(); + +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderLookup.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderLookup.java new file mode 100644 index 00000000000..140f596f3a4 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderLookup.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Binder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkState; + +/** + * A lookup of the provider for a type. Lookups are created explicitly in a module using + * {@link org.elasticsearch.util.guice.inject.Binder#getProvider(Class) getProvider()} statements: + *

+ *     Provider<PaymentService> paymentServiceProvider
+ *         = getProvider(PaymentService.class);
+ * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class ProviderLookup implements Element { + private final Object source; + private final Key key; + private Provider delegate; + + public ProviderLookup(Object source, Key key) { + this.source = checkNotNull(source, "source"); + this.key = checkNotNull(key, "key"); + } + + public Object getSource() { + return source; + } + + public Key getKey() { + return key; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + /** + * Sets the actual provider. + * + * @throws IllegalStateException if the delegate is already set + */ + public void initializeDelegate(Provider delegate) { + checkState(this.delegate == null, "delegate already initialized"); + this.delegate = checkNotNull(delegate, "delegate"); + } + + public void applyTo(Binder binder) { + initializeDelegate(binder.withSource(getSource()).getProvider(key)); + } + + /** + * Returns the delegate provider, or {@code null} if it has not yet been initialized. The delegate + * will be initialized when this element is processed, or otherwise used to create an injector. + */ + public Provider getDelegate() { + return delegate; + } + + /** + * Returns the looked up provider. The result is not valid until this lookup has been initialized, + * which usually happens when the injector is created. The provider will throw an {@code + * IllegalStateException} if you try to use it beforehand. + */ + public Provider getProvider() { + return new Provider() { + public T get() { + checkState(delegate != null, + "This Provider cannot be used until the Injector has been created."); + return delegate.get(); + } + + @Override public String toString() { + return "Provider<" + key.getTypeLiteral() + ">"; + } + }; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderWithDependencies.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderWithDependencies.java new file mode 100644 index 00000000000..595b485e57d --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ProviderWithDependencies.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Provider; + +/** + * A provider with dependencies on other injected types. If a {@link Provider} has dependencies that + * aren't specified in injections, this interface should be used to expose all dependencies. + * + * @since 2.0 + */ +public interface ProviderWithDependencies extends Provider, HasDependencies {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ScopeBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ScopeBinding.java new file mode 100644 index 00000000000..dc2b7dc2c7f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/ScopeBinding.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Scope; +import org.elasticsearch.util.guice.inject.Binder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.lang.annotation.Annotation; + +/** + * Registration of a scope annotation with the scope that implements it. Instances are created + * explicitly in a module using {@link org.elasticsearch.util.guice.inject.Binder#bindScope(Class, Scope) bindScope()} + * statements: + *
+ *     Scope recordScope = new RecordScope();
+ *     bindScope(RecordScoped.class, new RecordScope());
+ * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class ScopeBinding implements Element { + private final Object source; + private final Class annotationType; + private final Scope scope; + + ScopeBinding(Object source, Class annotationType, Scope scope) { + this.source = checkNotNull(source, "source"); + this.annotationType = checkNotNull(annotationType, "annotationType"); + this.scope = checkNotNull(scope, "scope"); + } + + public Object getSource() { + return source; + } + + public Class getAnnotationType() { + return annotationType; + } + + public Scope getScope() { + return scope; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).bindScope(annotationType, scope); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/StaticInjectionRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/StaticInjectionRequest.java new file mode 100644 index 00000000000..26b2e718ab6 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/StaticInjectionRequest.java @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.ConfigurationException; +import org.elasticsearch.util.guice.inject.Binder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import java.util.Set; + +/** + * A request to inject the static fields and methods of a type. Requests are created + * explicitly in a module using {@link org.elasticsearch.util.guice.inject.Binder#requestStaticInjection(Class[]) + * requestStaticInjection()} statements: + *
+ *     requestStaticInjection(MyLegacyService.class);
+ * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class StaticInjectionRequest implements Element { + private final Object source; + private final Class type; + + StaticInjectionRequest(Object source, Class type) { + this.source = checkNotNull(source, "source"); + this.type = checkNotNull(type, "type"); + } + + public Object getSource() { + return source; + } + + public Class getType() { + return type; + } + + /** + * Returns the static methods and fields of {@code type} that will be injected to fulfill this + * request. + * + * @return a possibly empty set of injection points. The set has a specified iteration order. All + * fields are returned and then all methods. Within the fields, supertype fields are returned + * before subtype fields. Similarly, supertype methods are returned before subtype methods. + * @throws ConfigurationException if there is a malformed injection point on {@code type}, such as + * a field with multiple binding annotations. The exception's {@link + * ConfigurationException#getPartialValue() partial value} is a {@code Set} + * of the valid injection points. + */ + public Set getInjectionPoints() throws ConfigurationException { + return InjectionPoint.forStaticMethodsAndFields(type); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).requestStaticInjection(type); + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeConverter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeConverter.java new file mode 100644 index 00000000000..ab41e878a15 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.TypeLiteral; + +/** + * Converts constant string values to a different type. + * + * @author crazybob@google.com (Bob Lee) + * @since 2.0 + */ +public interface TypeConverter { + + /** + * Converts a string value. Throws an exception if a conversion error occurs. + */ + Object convert(String value, TypeLiteral toType); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeConverterBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeConverterBinding.java new file mode 100644 index 00000000000..d03b7923b9b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeConverterBinding.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.Binder; +import static org.elasticsearch.util.guice.inject.internal.Preconditions.checkNotNull; +import org.elasticsearch.util.guice.inject.matcher.Matcher; + +/** + * Registration of type converters for matching target types. Instances are created + * explicitly in a module using {@link org.elasticsearch.util.guice.inject.Binder#convertToTypes(Matcher, + * TypeConverter) convertToTypes()} statements: + *
+ *     convertToTypes(Matchers.only(DateTime.class), new DateTimeConverter());
+ * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class TypeConverterBinding implements Element { + private final Object source; + private final Matcher> typeMatcher; + private final TypeConverter typeConverter; + + TypeConverterBinding(Object source, Matcher> typeMatcher, + TypeConverter typeConverter) { + this.source = checkNotNull(source, "source"); + this.typeMatcher = checkNotNull(typeMatcher, "typeMatcher"); + this.typeConverter = checkNotNull(typeConverter, "typeConverter"); + } + + public Object getSource() { + return source; + } + + public Matcher> getTypeMatcher() { + return typeMatcher; + } + + public TypeConverter getTypeConverter() { + return typeConverter; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).convertToTypes(typeMatcher, typeConverter); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeEncounter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeEncounter.java new file mode 100644 index 00000000000..9267867123f --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeEncounter.java @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.MembersInjector; +import org.elasticsearch.util.guice.inject.TypeLiteral; + +/** + * Context of an injectable type encounter. Enables reporting errors, registering injection + * listeners and binding method interceptors for injectable type {@code I}. It is an error to use + * an encounter after the {@link TypeListener#hear(TypeLiteral, TypeEncounter) hear()} method has + * returned. + * + * @param the injectable type encountered + * @since 2.0 + */ +public interface TypeEncounter { + + /** + * Records an error message for type {@code I} which will be presented to the user at a later + * time. Unlike throwing an exception, this enable us to continue configuring the Injector and + * discover more errors. Uses {@link String#format(String, Object[])} to insert the arguments + * into the message. + */ + void addError(String message, Object... arguments); + + /** + * Records an exception for type {@code I}, the full details of which will be logged, and the + * message of which will be presented to the user at a later time. If your type listener calls + * something that you worry may fail, you should catch the exception and pass it to this method. + */ + void addError(Throwable t); + + /** + * Records an error message to be presented to the user at a later time. + */ + void addError(Message message); + + /** + * Returns the provider used to obtain instances for the given injection key. The returned + * provider will not be valid until the injector has been created. The provider will throw an + * {@code IllegalStateException} if you try to use it beforehand. + */ + Provider getProvider(Key key); + + /** + * Returns the provider used to obtain instances for the given injection type. The returned + * provider will not be valid until the injetor has been created. The provider will throw an + * {@code IllegalStateException} if you try to use it beforehand. + */ + Provider getProvider(Class type); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * injector has been created. The members injector will throw an {@code IllegalStateException} + * if you try to use it beforehand. + * + * @param typeLiteral type to get members injector for + */ + MembersInjector getMembersInjector(TypeLiteral typeLiteral); + + /** + * Returns the members injector used to inject dependencies into methods and fields on instances + * of the given type {@code T}. The returned members injector will not be valid until the main + * injector has been created. The members injector will throw an {@code IllegalStateException} + * if you try to use it beforehand. + * + * @param type type to get members injector for + */ + MembersInjector getMembersInjector(Class type); + + /** + * Registers a members injector for type {@code I}. Guice will use the members injector after its + * performed its own injections on an instance of {@code I}. + */ + void register(MembersInjector membersInjector); + + /** + * Registers an injection listener for type {@code I}. Guice will notify the listener after all + * injections have been performed on an instance of {@code I}. + */ + void register(InjectionListener listener); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeListener.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeListener.java new file mode 100644 index 00000000000..fa80c256995 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeListener.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.TypeLiteral; + +/** + * Listens for Guice to encounter injectable types. If a given type has its constructor injected in + * one situation but only its methods and fields injected in another, Guice will notify this + * listener once. + * + *

Useful for extra type checking, {@linkplain TypeEncounter#register(InjectionListener) + * registering injection listeners}, and {@linkplain TypeEncounter#bindInterceptor( + * org.elasticsearch.util.guice.inject.matcher.Matcher, org.aopalliance.intercept.MethodInterceptor[]) + * binding method interceptors}. + * + * @since 2.0 + */ +public interface TypeListener { + + /** + * Invoked when Guice encounters a new type eligible for constructor or members injection. + * Called during injector creation (or afterwords if Guice encounters a type at run time and + * creates a JIT binding). + * + * @param type encountered by Guice + * @param encounter context of this encounter, enables reporting errors, registering injection + * listeners and binding method interceptors for {@code type}. + * + * @param the injectable type + */ + void hear(TypeLiteral type, TypeEncounter encounter); + +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeListenerBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeListenerBinding.java new file mode 100644 index 00000000000..b3de7e42482 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/TypeListenerBinding.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.TypeLiteral; +import org.elasticsearch.util.guice.inject.matcher.Matcher; + +/** + * Binds types (picked using a Matcher) to an type listener. Registrations are created explicitly in + * a module using {@link org.elasticsearch.util.guice.inject.Binder#bindListener(Matcher, TypeListener)} statements: + * + *

+ *     register(only(new TypeLiteral<PaymentService<CreditCard>>() {}), listener);
+ * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class TypeListenerBinding implements Element { + + private final Object source; + private final Matcher> typeMatcher; + private final TypeListener listener; + + TypeListenerBinding(Object source, TypeListener listener, + Matcher> typeMatcher) { + this.source = source; + this.listener = listener; + this.typeMatcher = typeMatcher; + } + + /** Returns the registered listener. */ + public TypeListener getListener() { + return listener; + } + + /** Returns the type matcher which chooses which types the listener should be notified of. */ + public Matcher> getTypeMatcher() { + return typeMatcher; + } + + public Object getSource() { + return source; + } + + public T acceptVisitor(ElementVisitor visitor) { + return visitor.visit(this); + } + + public void applyTo(Binder binder) { + binder.withSource(getSource()).bindListener(typeMatcher, listener); + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/UntargettedBinding.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/UntargettedBinding.java new file mode 100644 index 00000000000..d32dc9f2c30 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/UntargettedBinding.java @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.spi; + +import org.elasticsearch.util.guice.inject.Binding; + +/** + * An untargetted binding. This binding indicates that the injector should use its implicit binding + * strategies to resolve injections. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public interface UntargettedBinding extends Binding {} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/package-info.java new file mode 100644 index 00000000000..55ff9e9b9dc --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/spi/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2006 Google Inc. + * + * 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. + */ + +/** + * Guice service provider interface + */ + +package org.elasticsearch.util.guice.inject.spi; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Modules.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Modules.java new file mode 100644 index 00000000000..3b49453218a --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Modules.java @@ -0,0 +1,270 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.util; + +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Binder; +import org.elasticsearch.util.guice.inject.Binding; +import org.elasticsearch.util.guice.inject.Key; +import org.elasticsearch.util.guice.inject.Module; +import org.elasticsearch.util.guice.inject.Scope; +import org.elasticsearch.util.guice.inject.PrivateBinder; +import org.elasticsearch.util.guice.inject.spi.DefaultBindingScopingVisitor; +import org.elasticsearch.util.guice.inject.spi.DefaultElementVisitor; +import org.elasticsearch.util.guice.inject.spi.Element; +import org.elasticsearch.util.guice.inject.spi.Elements; +import org.elasticsearch.util.guice.inject.spi.ScopeBinding; +import org.elasticsearch.util.guice.inject.spi.PrivateElements; +import org.elasticsearch.util.gcommon.collect.ImmutableSet; +import org.elasticsearch.util.gcommon.collect.Lists; +import org.elasticsearch.util.gcommon.collect.Maps; +import org.elasticsearch.util.gcommon.collect.Sets; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Static utility methods for creating and working with instances of {@link Module}. + * + * @author jessewilson@google.com (Jesse Wilson) + * @since 2.0 + */ +public final class Modules { + private Modules() {} + + public static final Module EMPTY_MODULE = new Module() { + public void configure(Binder binder) {} + }; + + /** + * Returns a builder that creates a module that overlays override modules over the given + * modules. If a key is bound in both sets of modules, only the binding from the override modules + * is kept. This can be used to replace the bindings of a production module with test bindings: + *
+   * Module functionalTestModule
+   *     = Modules.override(new ProductionModule()).with(new TestModule());
+   * 
+ * + *

Prefer to write smaller modules that can be reused and tested without overrides. + * + * @param modules the modules whose bindings are open to be overridden + */ + public static OverriddenModuleBuilder override(Module... modules) { + return new RealOverriddenModuleBuilder(Arrays.asList(modules)); + } + + /** + * Returns a builder that creates a module that overlays override modules over the given + * modules. If a key is bound in both sets of modules, only the binding from the override modules + * is kept. This can be used to replace the bindings of a production module with test bindings: + *

+   * Module functionalTestModule
+   *     = Modules.override(getProductionModules()).with(getTestModules());
+   * 
+ * + *

Prefer to write smaller modules that can be reused and tested without overrides. + * + * @param modules the modules whose bindings are open to be overridden + */ + public static OverriddenModuleBuilder override(Iterable modules) { + return new RealOverriddenModuleBuilder(modules); + } + + /** + * Returns a new module that installs all of {@code modules}. + */ + public static Module combine(Module... modules) { + return combine(ImmutableSet.of(modules)); + } + + /** + * Returns a new module that installs all of {@code modules}. + */ + public static Module combine(Iterable modules) { + final Set modulesSet = ImmutableSet.copyOf(modules); + return new Module() { + public void configure(Binder binder) { + binder = binder.skipSources(getClass()); + for (Module module : modulesSet) { + binder.install(module); + } + } + }; + } + + /** + * See the EDSL example at {@link Modules#override(Module[]) override()}. + */ + public interface OverriddenModuleBuilder { + + /** + * See the EDSL example at {@link Modules#override(Module[]) override()}. + */ + Module with(Module... overrides); + + /** + * See the EDSL example at {@link Modules#override(Module[]) override()}. + */ + Module with(Iterable overrides); + } + + private static final class RealOverriddenModuleBuilder implements OverriddenModuleBuilder { + private final ImmutableSet baseModules; + + private RealOverriddenModuleBuilder(Iterable baseModules) { + this.baseModules = ImmutableSet.copyOf(baseModules); + } + + public Module with(Module... overrides) { + return with(Arrays.asList(overrides)); + } + + public Module with(final Iterable overrides) { + return new AbstractModule() { + @Override + public void configure() { + final List elements = Elements.getElements(baseModules); + final List overrideElements = Elements.getElements(overrides); + + final Set overriddenKeys = Sets.newHashSet(); + final Set> overridesScopeAnnotations = Sets.newHashSet(); + + // execute the overrides module, keeping track of which keys and scopes are bound + new ModuleWriter(binder()) { + @Override public Void visit(Binding binding) { + overriddenKeys.add(binding.getKey()); + return super.visit(binding); + } + + @Override public Void visit(ScopeBinding scopeBinding) { + overridesScopeAnnotations.add(scopeBinding.getAnnotationType()); + return super.visit(scopeBinding); + } + + @Override public Void visit(PrivateElements privateElements) { + overriddenKeys.addAll(privateElements.getExposedKeys()); + return super.visit(privateElements); + } + }.writeAll(overrideElements); + + // execute the original module, skipping all scopes and overridden keys. We only skip each + // overridden binding once so things still blow up if the module binds the same thing + // multiple times. + final Map scopeInstancesInUse = Maps.newHashMap(); + final List scopeBindings = Lists.newArrayList(); + new ModuleWriter(binder()) { + @Override public Void visit(Binding binding) { + if (!overriddenKeys.remove(binding.getKey())) { + super.visit(binding); + + // Record when a scope instance is used in a binding + Scope scope = getScopeInstanceOrNull(binding); + if (scope != null) { + scopeInstancesInUse.put(scope, binding.getSource()); + } + } + + return null; + } + + @Override public Void visit(PrivateElements privateElements) { + PrivateBinder privateBinder = binder.withSource(privateElements.getSource()) + .newPrivateBinder(); + + Set> skippedExposes = Sets.newHashSet(); + + for (Key key : privateElements.getExposedKeys()) { + if (overriddenKeys.remove(key)) { + skippedExposes.add(key); + } else { + privateBinder.withSource(privateElements.getExposedSource(key)).expose(key); + } + } + + // we're not skipping deep exposes, but that should be okay. If we ever need to, we + // have to search through this set of elements for PrivateElements, recursively + for (Element element : privateElements.getElements()) { + if (element instanceof Binding + && skippedExposes.contains(((Binding) element).getKey())) { + continue; + } + element.applyTo(privateBinder); + } + + return null; + } + + @Override public Void visit(ScopeBinding scopeBinding) { + scopeBindings.add(scopeBinding); + return null; + } + }.writeAll(elements); + + // execute the scope bindings, skipping scopes that have been overridden. Any scope that + // is overridden and in active use will prompt an error + new ModuleWriter(binder()) { + @Override public Void visit(ScopeBinding scopeBinding) { + if (!overridesScopeAnnotations.remove(scopeBinding.getAnnotationType())) { + super.visit(scopeBinding); + } else { + Object source = scopeInstancesInUse.get(scopeBinding.getScope()); + if (source != null) { + binder().withSource(source).addError( + "The scope for @%s is bound directly and cannot be overridden.", + scopeBinding.getAnnotationType().getSimpleName()); + } + } + return null; + } + }.writeAll(scopeBindings); + + // TODO: bind the overridden keys using multibinder + } + + private Scope getScopeInstanceOrNull(Binding binding) { + return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor() { + public Scope visitScope(Scope scope) { + return scope; + } + }); + } + }; + } + } + + private static class ModuleWriter extends DefaultElementVisitor { + protected final Binder binder; + + ModuleWriter(Binder binder) { + this.binder = binder; + } + + @Override protected Void visitOther(Element element) { + element.applyTo(binder); + return null; + } + + void writeAll(Iterable elements) { + for (Element element : elements) { + element.acceptVisitor(this); + } + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Providers.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Providers.java new file mode 100644 index 00000000000..dcecdf07ac5 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Providers.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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. + */ + +package org.elasticsearch.util.guice.inject.util; + +import org.elasticsearch.util.guice.inject.Provider; + +/** + * Static utility methods for creating and working with instances of + * {@link Provider}. + * + * @author Kevin Bourrillion (kevinb9n@gmail.com) + * @since 2.0 + */ +public final class Providers { + + private Providers() {} + + /** + * Returns a provider which always provides {@code instance}. This should not + * be necessary to use in your application, but is helpful for several types + * of unit tests. + * + * @param instance the instance that should always be provided. This is also + * permitted to be null, to enable aggressive testing, although in real + * life a Guice-supplied Provider will never return null. + */ + public static Provider of(final T instance) { + return new Provider() { + public T get() { + return instance; + } + + @Override public String toString() { + return "of(" + instance + ")"; + } + }; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Types.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Types.java new file mode 100644 index 00000000000..0870a2ca66b --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/Types.java @@ -0,0 +1,133 @@ +/** + * Copyright (C) 2008 Google Inc. + * + * 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. + */ + + +package org.elasticsearch.util.guice.inject.util; + +import org.elasticsearch.util.guice.inject.Provider; +import org.elasticsearch.util.guice.inject.internal.MoreTypes; +import org.elasticsearch.util.guice.inject.internal.MoreTypes.GenericArrayTypeImpl; +import org.elasticsearch.util.guice.inject.internal.MoreTypes.ParameterizedTypeImpl; +import org.elasticsearch.util.guice.inject.internal.MoreTypes.WildcardTypeImpl; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Static methods for working with types. + * + * @author crazybob@google.com (Bob Lee) + * @since 2.0 + */ +public final class Types { + private Types() {} + + /** + * Returns a new parameterized type, applying {@code typeArguments} to + * {@code rawType}. The returned type does not have an owner type. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) { + return newParameterizedTypeWithOwner(null, rawType, typeArguments); + } + + /** + * Returns a new parameterized type, applying {@code typeArguments} to + * {@code rawType} and enclosed by {@code ownerType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType newParameterizedTypeWithOwner( + Type ownerType, Type rawType, Type... typeArguments) { + return new ParameterizedTypeImpl(ownerType, rawType, typeArguments); + } + + /** + * Returns an array type whose elements are all instances of + * {@code componentType}. + * + * @return a {@link java.io.Serializable serializable} generic array type. + */ + public static GenericArrayType arrayOf(Type componentType) { + return new GenericArrayTypeImpl(componentType); + } + + /** + * Returns a type that represents an unknown type that extends {@code bound}. + * For example, if {@code bound} is {@code CharSequence.class}, this returns + * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class}, + * this returns {@code ?}, which is shorthand for {@code ? extends Object}. + */ + public static WildcardType subtypeOf(Type bound) { + return new WildcardTypeImpl(new Type[] { bound }, MoreTypes.EMPTY_TYPE_ARRAY); + } + + /** + * Returns a type that represents an unknown supertype of {@code bound}. For + * example, if {@code bound} is {@code String.class}, this returns {@code ? + * super String}. + */ + public static WildcardType supertypeOf(Type bound) { + return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound }); + } + + /** + * Returns a type modelling a {@link List} whose elements are of type + * {@code elementType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType listOf(Type elementType) { + return newParameterizedType(List.class, elementType); + } + + /** + * Returns a type modelling a {@link Set} whose elements are of type + * {@code elementType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType setOf(Type elementType) { + return newParameterizedType(Set.class, elementType); + } + + /** + * Returns a type modelling a {@link Map} whose keys are of type + * {@code keyType} and whose values are of type {@code valueType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType mapOf(Type keyType, Type valueType) { + return newParameterizedType(Map.class, keyType, valueType); + } + + // for other custom collections types, use newParameterizedType() + + /** + * Returns a type modelling a {@link Provider} that provides elements of type + * {@code elementType}. + * + * @return a {@link java.io.Serializable serializable} parameterized type. + */ + public static ParameterizedType providerOf(Type providedType) { + return newParameterizedType(Provider.class, providedType); + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/package-info.java new file mode 100644 index 00000000000..022c0cc2c59 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/guice/inject/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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. + */ + +/** + * Helper methods for working with Guice. + */ +package org.elasticsearch.util.guice.inject.util; \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/settings/SettingsModule.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/settings/SettingsModule.java index 0a5abce1696..faf7c55a494 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/settings/SettingsModule.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/settings/SettingsModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.util.settings; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; /** * A module that binds the provided settings to the {@link Settings} interface. diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/analysis/AnalysisModuleTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/analysis/AnalysisModuleTests.java index ed78de66e15..e4c102302ee 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/analysis/AnalysisModuleTests.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/analysis/AnalysisModuleTests.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.analysis; -import com.google.inject.Guice; -import com.google.inject.Injector; +import org.elasticsearch.util.guice.inject.Guice; +import org.elasticsearch.util.guice.inject.Injector; import org.apache.lucene.analysis.Analyzer; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNameModule; diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/IndexQueryParserModuleTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/IndexQueryParserModuleTests.java index 955a510a06e..decf4116a93 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/IndexQueryParserModuleTests.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/IndexQueryParserModuleTests.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.query.json.guice; -import com.google.inject.Guice; -import com.google.inject.Injector; +import org.elasticsearch.util.guice.inject.Guice; +import org.elasticsearch.util.guice.inject.Injector; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNameModule; import org.elasticsearch.index.analysis.AnalysisModule; diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonFilterParser.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonFilterParser.java index 405a3266a0a..50fae548717 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonFilterParser.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonFilterParser.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.query.json.guice; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.search.Filter; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonQueryParser.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonQueryParser.java index 8e566dfd1ae..9503ab11fcd 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonQueryParser.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/query/json/guice/MyJsonQueryParser.java @@ -19,8 +19,8 @@ package org.elasticsearch.index.query.json.guice; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import org.elasticsearch.util.guice.inject.Inject; +import org.elasticsearch.util.guice.inject.assistedinject.Assisted; import org.apache.lucene.search.Query; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/util/guice/InjectorsTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/util/guice/InjectorsTests.java index 00a9e9f7900..db851c069a6 100644 --- a/modules/elasticsearch/src/test/java/org/elasticsearch/util/guice/InjectorsTests.java +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/util/guice/InjectorsTests.java @@ -19,11 +19,11 @@ package org.elasticsearch.util.guice; -import com.google.inject.AbstractModule; -import com.google.inject.BindingAnnotation; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.matcher.Matchers; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.BindingAnnotation; +import org.elasticsearch.util.guice.inject.Guice; +import org.elasticsearch.util.guice.inject.Injector; +import org.elasticsearch.util.guice.inject.matcher.Matchers; import org.testng.annotations.Test; import java.lang.annotation.Documented; diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/TwoInstanceUnbalancedShardsEmbeddedSearchTests.java b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/TwoInstanceUnbalancedShardsEmbeddedSearchTests.java index edf5520f0bf..6660c665b37 100644 --- a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/TwoInstanceUnbalancedShardsEmbeddedSearchTests.java +++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/TwoInstanceUnbalancedShardsEmbeddedSearchTests.java @@ -20,8 +20,8 @@ package org.elasticsearch.test.integration.search; import org.elasticsearch.util.gcommon.collect.ImmutableMap; -import com.google.inject.AbstractModule; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.client.Client; import org.elasticsearch.client.Requests; import org.elasticsearch.cluster.ClusterService; diff --git a/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscovery.java b/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscovery.java index 7b9c22c8a70..b9c1d937a63 100644 --- a/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscovery.java +++ b/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscovery.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery.jgroups; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchIllegalStateException; import org.elasticsearch.cluster.*; diff --git a/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscoveryModule.java b/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscoveryModule.java index b89ba308ca0..c0b36a1b0a5 100644 --- a/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscoveryModule.java +++ b/plugins/discovery/jgroups/src/main/java/org/elasticsearch/discovery/jgroups/JgroupsDiscoveryModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.discovery.jgroups; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.discovery.Discovery; import org.elasticsearch.util.settings.Settings; diff --git a/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsIndexModule.java b/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsIndexModule.java index 053a90779f9..e601f969da3 100644 --- a/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsIndexModule.java +++ b/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsIndexModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.plugin.attachments; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.plugin.attachments.index.mapper.JsonAttachmentMapperService; /** diff --git a/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsPlugin.java b/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsPlugin.java index 4b5817eb7aa..44ab8353e29 100644 --- a/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsPlugin.java +++ b/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/AttachmentsPlugin.java @@ -19,7 +19,7 @@ package org.elasticsearch.plugin.attachments; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.plugins.AbstractPlugin; import java.util.Collection; diff --git a/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/index/mapper/JsonAttachmentMapperService.java b/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/index/mapper/JsonAttachmentMapperService.java index 91ffaebe111..744fb4a6241 100644 --- a/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/index/mapper/JsonAttachmentMapperService.java +++ b/plugins/mapper/attachments/src/main/java/org/elasticsearch/plugin/attachments/index/mapper/JsonAttachmentMapperService.java @@ -19,7 +19,7 @@ package org.elasticsearch.plugin.attachments.index.mapper; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.index.AbstractIndexComponent; import org.elasticsearch.index.Index; import org.elasticsearch.index.mapper.MapperService; diff --git a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedPlugin.java b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedPlugin.java index 2e347d0e2c6..24e5de9302a 100644 --- a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedPlugin.java +++ b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedPlugin.java @@ -19,7 +19,7 @@ package org.elasticsearch.memcached; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.plugins.AbstractPlugin; import org.elasticsearch.util.component.LifecycleComponent; import org.elasticsearch.util.settings.Settings; diff --git a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServer.java b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServer.java index 57ed95318cc..db6d5de78a6 100644 --- a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServer.java +++ b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServer.java @@ -19,7 +19,7 @@ package org.elasticsearch.memcached; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfo; import org.elasticsearch.rest.RestController; diff --git a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServerModule.java b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServerModule.java index 9c4c6c6df45..a92841c7faf 100644 --- a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServerModule.java +++ b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/MemcachedServerModule.java @@ -19,8 +19,8 @@ package org.elasticsearch.memcached; -import com.google.inject.AbstractModule; -import com.google.inject.Module; +import org.elasticsearch.util.guice.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.Module; import org.elasticsearch.util.Classes; import org.elasticsearch.util.settings.Settings; diff --git a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransport.java b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransport.java index 65473eeba37..9b04ac5153c 100644 --- a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransport.java +++ b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransport.java @@ -19,7 +19,7 @@ package org.elasticsearch.memcached.netty; -import com.google.inject.Inject; +import org.elasticsearch.util.guice.inject.Inject; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.http.BindHttpException; import org.elasticsearch.memcached.MemcachedServerTransport; diff --git a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransportModule.java b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransportModule.java index 2c210ce8b6f..8fcfc4891f0 100644 --- a/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransportModule.java +++ b/plugins/transport/memcached/src/main/java/org/elasticsearch/memcached/netty/NettyMemcachedServerTransportModule.java @@ -19,7 +19,7 @@ package org.elasticsearch.memcached.netty; -import com.google.inject.AbstractModule; +import org.elasticsearch.util.guice.inject.AbstractModule; import org.elasticsearch.memcached.MemcachedServerTransport; /**