
com.couchbase.client.java.datastructures.collections.CouchbaseArraySet Maven / Gradle / Ivy
/*
* Copyright (c) 2016 Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.couchbase.client.java.datastructures.collections;
import java.util.AbstractSet;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Set;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.core.message.ResponseStatus;
import com.couchbase.client.core.message.kv.subdoc.multi.Lookup;
import com.couchbase.client.core.message.kv.subdoc.multi.Mutation;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.datastructures.collections.iterators.JsonArrayDocumentIterator;
import com.couchbase.client.java.document.JsonArrayDocument;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.document.json.JsonValue;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.error.DocumentAlreadyExistsException;
import com.couchbase.client.java.subdoc.DocumentFragment;
/**
* A CouchbaseArraySet is a {@link Set} backed by a {@link Bucket Couchbase} document (more
* specifically a {@link JsonArrayDocument JSON array}).
*
* Note that a CouchbaseArraySet is restricted to primitive types (the types that a {@link JsonArray JSON array}
* can contain, except {@link JsonObject} and {@link JsonArray}). null entries are supported.
*
* @param the type of values in the set.
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
* @since 2.3.6
*/
@InterfaceStability.Committed
@InterfaceAudience.Public
public class CouchbaseArraySet extends AbstractSet {
private static final int MAX_OPTIMISTIC_LOCKING_ATTEMPTS = Integer.parseInt(System.getProperty("com.couchbase.datastructureCASRetryLimit", "10"));
private final String id;
private final Bucket bucket;
/**
* Create a new {@link CouchbaseArraySet}, backed by the document identified by id
* in the given Couchbase bucket
. Note that if the document already exists,
* its content will be used as initial content for this collection. Otherwise it is created empty.
*
* @param id the id of the Couchbase document to back the set.
* @param bucket the {@link Bucket} through which to interact with the document.
*/
public CouchbaseArraySet(String id, Bucket bucket) {
this.id = id;
this.bucket = bucket;
try {
this.bucket.insert(JsonArrayDocument.create(id, JsonArray.empty()));
} catch (DocumentAlreadyExistsException e) {
//use a pre-existing document
}
}
/**
* Create a new {@link CouchbaseArraySet}, backed by the document identified by id
* in the given Couchbase bucket
. Note that if the document already exists, its content is
* reset to the values copied from the given data
Map.
*
* A null or empty data map will re-initialize any pre-existing document to an empty content.
*
* @param id the id of the Couchbase document to back the set.
* @param bucket the {@link Bucket} through which to interact with the document.
* @param initialData Set of the elements to initially store in the CouchbaseArraySet.
*/
public CouchbaseArraySet(String id, Bucket bucket, Set extends T> initialData) {
this.id = id;
this.bucket = bucket;
JsonArray data = JsonArray.create();
if (initialData != null && !initialData.isEmpty()) {
for (Object o : initialData) {
enforcePrimitive(o);
data.add(o);
}
}
bucket.upsert(JsonArrayDocument.create(id, data));
}
@Override
public int size() {
//TODO use subdoc GET_COUNT when available
JsonArrayDocument current = bucket.get(id, JsonArrayDocument.class);
return current.content().size();
}
@Override
public boolean isEmpty() {
DocumentFragment current = bucket.lookupIn(id).exists("[0]").execute();
return current.status(0) == ResponseStatus.SUBDOC_PATH_NOT_FOUND;
}
@Override
public boolean contains(Object t) {
//TODO subpar implementation for a Set, use ARRAY_CONTAINS when available
enforcePrimitive(t);
JsonArrayDocument current = bucket.get(id, JsonArrayDocument.class);
for (Object in : current.content()) {
if (safeEquals(in, t)) {
return true;
}
}
return false;
}
@Override
public Iterator iterator() {
return new JsonArrayDocumentIterator(bucket, id);
}
@Override
public boolean add(T t) {
enforcePrimitive(t);
for (int i = 0; i < MAX_OPTIMISTIC_LOCKING_ATTEMPTS; i++) {
try {
JsonArrayDocument current = bucket.get(id, JsonArrayDocument.class);
long cas = current.cas();
//Care not to use toList, as it will convert internal JsonObject/JsonArray to Map/List
boolean absent = true;
for (Object in : current.content()) {
if (safeEquals(in, t)) {
absent = false;
break;
}
}
if (absent) {
DocumentFragment result = bucket.mutateIn(id)
.arrayAppend("", t, true) //append at the root array
.withCas(cas)
.execute();
return true;
} else {
return false;
}
} catch (CASMismatchException e) {
//retry
}
}
throw new ConcurrentModificationException("Couldn't perform add in less than " + MAX_OPTIMISTIC_LOCKING_ATTEMPTS + " iterations");
}
@Override
public boolean remove(Object t) {
enforcePrimitive(t);
for (int i = 0; i < MAX_OPTIMISTIC_LOCKING_ATTEMPTS; i++) {
try {
JsonArrayDocument current = bucket.get(id, JsonArrayDocument.class);
long cas = current.cas();
int index = 0;
boolean found = false;
Iterator
© 2015 - 2025 Weber Informatics LLC | Privacy Policy