package com.bazaarvoice.commons.data.model.json.schema.types;
import com.bazaarvoice.commons.data.model.json.GenericJSONArrayList;
import com.bazaarvoice.commons.data.model.json.schema.JSONSchema;
import com.bazaarvoice.commons.data.model.json.schema.JSONSchemaType;
import com.bazaarvoice.commons.data.model.json.schema.JSONSchemas;
import com.bazaarvoice.commons.data.model.json.schema.validation.ResultType;
import com.bazaarvoice.commons.data.model.json.schema.validation.ValidationResult;
import com.bazaarvoice.commons.data.model.json.schema.validation.ValidationResults;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.json.JSONArray;
import javax.annotation.Nullable;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class JSONSchemaArrayType implements JSONSchemaType<JSONSchemaArrayType> {
private List<JSONSchema> _items = Collections.emptyList();
private JSONSchema _additionalItemsSchema;
/**
* Determines whether or not this array is a tuple.
*/
public boolean isTuple() {
return _items.size() > 1;
}
public List<JSONSchema> getItems() {
return _items;
}
public void setItems(Iterable<JSONSchema> items) {
_items = Lists.newArrayList(items);
}
public JSONSchemaArrayType items(Iterable<JSONSchema> items) {
setItems(items);
return this;
}
public JSONSchemaArrayType items(JSONSchema... items) {
setItems(Arrays.asList(items));
return this;
}
/**
* Assumes the array isn't a tuple and returns the schema for the items allowed in this array.
*/
public JSONSchema getItem() {
if (_items.isEmpty()) {
return new JSONSchema();
}
return _items.get(0);
}
public void setItem(JSONSchema item) {
_items = Collections.singletonList(item);
}
public JSONSchemaArrayType item(JSONSchema item) {
setItem(item);
return this;
}
public JSONSchema getAdditionalItemsSchema() {
return _additionalItemsSchema;
}
public void setAdditionalItemsSchema(JSONSchema additionalItemsSchema) {
_additionalItemsSchema = additionalItemsSchema;
}
/**
* When a tuple, sets the schema for any additional items. If null, additional items are not allowed. Defaults to ANY.
*/
public JSONSchemaArrayType additionalItemsSchema(JSONSchema additionalItemsSchema) {
setAdditionalItemsSchema(additionalItemsSchema);
return this;
}
public JSONSchemaArrayType allowAdditionalItems() {
setAdditionalItemsSchema(new JSONSchema());
return this;
}
public JSONSchemaArrayType disallowAdditionalItems() {
setAdditionalItemsSchema(null);
return this;
}
@Override
public JSONSchemaArrayType clone() {
return new JSONSchemaArrayType().items(Iterables.transform(_items, JSONSchemas.CloningFunction.INSTANCE)).
additionalItemsSchema(_additionalItemsSchema != null ? _additionalItemsSchema.clone() : null);
}
@Override
public JSONSchemaArrayType merge(@Nullable JSONSchemaArrayType parentType) {
if (parentType == null) {
return this;
}
int size = _items.size();
if (size == parentType._items.size()) {
// only merge the parent type's items if both are not a tuple or the tuples are the same arity
for (int index = 0; index < size; index++) {
_items.get(index).merge(parentType._items.get(index));
}
} else if (size == 0) {
setItems(Collections2.transform(parentType._items, JSONSchemas.CloningFunction.INSTANCE));
}
if (_additionalItemsSchema != null) {
if (_additionalItemsSchema.isEmpty() && parentType._additionalItemsSchema == null) {
// null value means disallow additional items, so parent null convey when this is the "empty" schema
_additionalItemsSchema = null;
} else {
_additionalItemsSchema.merge(parentType._additionalItemsSchema);
}
}
return this;
}
@Override
public void validate(JSONSchema schema, Object obj, String path, ValidationResults results) {
Collection coll;
if (obj instanceof JSONArray) {
coll = new GenericJSONArrayList((JSONArray) obj);
} else if (obj instanceof Collection) {
coll = (Collection) obj;
} else {
final Iterable iter = (Iterable) obj;
coll = new AbstractCollection() {
@Override
public Iterator iterator() {
return iter.iterator();
}
@Override
public int size() {
return Iterables.size(iter);
}
};
}
if (isTuple()) {
int tupleSize = _items.size();
int collSize = _items.size();
if (tupleSize > collSize) {
results.addResult(new ValidationResult().type(ResultType.CONSTRAINT_VIOLATION).path(path).message("Tuple is not the correct size " + tupleSize + ": " + coll));
}
Iterator<JSONSchema> tupleIter = _items.iterator();
Iterator collIter = coll.iterator();
int index = 0;
while (tupleIter.hasNext() && collIter.hasNext()) {
JSONSchema tupleSchema = tupleIter.next();
Object tupleObj = collIter.next();
tupleSchema.validate(tupleObj, path + "[" + index + "]", results);
index++;
}
if (_additionalItemsSchema != null) {
while (collIter.hasNext()) {
Object additionalObj = collIter.next();
_additionalItemsSchema.validate(additionalObj, path + "[" + index + "]", results);
index++;
}
} else if (collIter.hasNext()) {
results.addResult(new ValidationResult().type(ResultType.CONSTRAINT_VIOLATION).path(path).message("Tuple does not allow additional items:" + coll));
}
} else {
int index = 0;
JSONSchema itemSchema = getItem();
for (final Object collObj : coll) {
itemSchema.validate(collObj, path + "[" + index + "]", results);
index++;
}
}
}
@Override
public String toString() {
return super.toString() +
"[isTuple=" + isTuple() + "]" +
"[items=" + _items + "]" +
"[additionalItemsSchema=" + _additionalItemsSchema + "]";
}
}