/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.struct;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Stream;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;
import divconq.lang.BigDateTime;
import divconq.lang.Memory;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.schema.DataType;
import divconq.schema.TypeOptionsList;
import divconq.script.StackEntry;
import divconq.struct.builder.BuilderStateException;
import divconq.struct.builder.ICompositeBuilder;
import divconq.struct.scalar.IntegerStruct;
import divconq.struct.scalar.NullStruct;
import divconq.util.ClassicIterableAdapter;
import divconq.util.IAsyncIterable;
import divconq.util.StringUtil;
import divconq.xml.XElement;
/**
* DivConq uses a specialized type system that provides type consistency across services
* (including web services), database fields and stored procedures, as well as scripting.
*
* All scalars (including primitives) and composites (collections) are wrapped by some
* subclass of Struct. List/array collections are expressed by this class.
* This class is analogous to an Array in JSON but may contain type information as well,
* similar to Yaml.
*
* TODO link to blog entries.
*
* @author Andy
*
*/
public class ListStruct extends CompositeStruct implements IItemCollection, Iterable<Object> {
protected List<Struct> items = new CopyOnWriteArrayList<Struct>(); // TODO can we make a more efficient list (one that allows modifications but won't crash an iterator)
@Override
public DataType getType() {
if (this.explicitType != null)
return super.getType();
// implied only, not explicit
return OperationContext.get().getSchema().getType("AnyList");
}
/**
* Provide data type info (schema for fields) and a list of initial items
*
* @param type field schema
* @param items initial values
*/
public ListStruct(DataType type, Object... items) {
super(type);
this.addItem(items);
}
public ListStruct(DataType type, Collection<? extends Object> items) {
super(type);
this.addCollection(items);
}
/**
* Optionally provide a list of initial items
*
* @param items initial values
*/
public ListStruct(Object... items) {
this.addItem(items);
}
public ListStruct(Collection<? extends Object> items) {
this.addCollection(items);
}
/* (non-Javadoc)
* @see divconq.struct.CompositeStruct#select(divconq.struct.PathPart[])
*/
@Override
public Struct select(PathPart... path) {
if (path.length == 0)
return this;
PathPart part = path[0];
OperationResult log = part.getLog();
// not allowed
if (log == null)
return NullStruct.instance;
String fld = part.getField();
if ("Length".equals(fld))
return new IntegerStruct(this.items.size());
if (fld != null) {
log.warnTr(501, this);
return NullStruct.instance;
}
int idx = part.getIndex();
if (idx >= this.items.size()) {
log.warnTr(502, part.getIndex());
return NullStruct.instance;
}
Struct o = this.items.get(idx);
if (path.length == 1)
return o;
if (o instanceof CompositeStruct)
return ((CompositeStruct)o).select(Arrays.copyOfRange(path, 1, path.length));
log.warnTr(503, o);
return NullStruct.instance;
}
public Stream<Struct> structStream() {
return this.items.stream();
}
public Stream<RecordStruct> recordStream() {
return this.items.stream().map(p -> (RecordStruct)p);
}
public Stream<String> stringStream() {
return this.items.stream().map(p -> Struct.objectToString(p));
}
public Stream<Long> integerStream() {
return this.items.stream().map(p -> Struct.objectToInteger(p));
}
/* (non-Javadoc)
* @see divconq.struct.Struct#isBlank()
*/
@Override
public boolean isEmpty() {
return (this.items.size() == 0);
}
/* (non-Javadoc)
* @see divconq.struct.builder.ICompositeOutput#toBuilder(divconq.struct.builder.ICompositeBuilder)
*/
@Override
public void toBuilder(ICompositeBuilder builder) throws BuilderStateException {
builder.startList();
for (Object o : this.items)
builder.value(o);
builder.endList();
}
/**
* Attempt to add items to the list, but if there is a schema the items
* must match the schema.
*
* @param items to add
* @return log of the result of the call (check hasErrors)
*/
public void addItem(Object... items) {
for (Object o : items) {
Object value = o;
Struct svalue = null;
if (value instanceof ICompositeBuilder)
value = ((ICompositeBuilder)value).toLocal();
if (this.explicitType != null) {
TypeOptionsList itms = this.explicitType.getItems();
if (itms != null) {
Struct sv = itms.wrap(value);
if (sv != null)
svalue = sv;
}
}
if (svalue == null)
svalue = Struct.objectToStruct(value);
this.items.add(svalue);
}
}
public ListStruct withItems(Object... items) {
this.addItem(items);
return this;
}
public OperationResult addCollection(Collection<? extends Object> coll) {
OperationResult or = new OperationResult();
for (Object o : coll)
this.addItem(o); // extra slow, enhance TODO
return or;
}
public OperationResult addCollection(ListStruct coll) {
OperationResult or = new OperationResult();
for (Struct o : coll.getItems())
this.addItem(o); // extra slow, enhance TODO
return or;
}
public void replaceItem(int i, Struct o) {
if (i < this.items.size())
this.items.set(i, o);
}
/**
*
* @return collection of all the items the list holds
*/
@Override
public Iterable<Struct> getItems() {
return this.items;
}
@Override
public IAsyncIterable<Struct> getItemsAsync() {
return new ClassicIterableAdapter<Struct>(this.items);
}
/**
*
* @param idx position in list of the item desired (0 based)
* @return the struct for that field
*/
public Struct getItem(int idx) {
if ((idx >= this.items.size()) || (idx < 0))
return null;
return this.items.get(idx);
}
public Object getAt(int idx) {
if ((idx >= this.items.size()) || (idx < 0))
return null;
Struct v = this.items.get(idx);
if (v == null)
return null;
if (v instanceof CompositeStruct)
return v;
return ((ScalarStruct) v).getGenericValue();
}
/**
*
* @param idx position in list of the item desired (0 based)
* @return true if an item is at that position
*/
public boolean hasItem(int idx) {
if (idx >= this.items.size())
return false;
return true;
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Integer (DivConq thinks of integers as 64bit)
*/
public Long getItemAsInteger(int idx) {
return Struct.objectToInteger(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as BigInteger
*/
public BigInteger getItemAsBigInteger(int idx) {
return Struct.objectToBigInteger(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as BigDecimal
*/
public BigDecimal getItemAsDecimal(int idx) {
return Struct.objectToDecimal(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Boolean
*/
public Boolean getItemAsBoolean(int idx) {
return Struct.objectToBoolean(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Datetime
*/
public DateTime getItemAsDateTime(int idx) {
return Struct.objectToDateTime(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Datetime
*/
public BigDateTime getItemAsBigDateTime(int idx) {
return Struct.objectToBigDateTime(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Date
*/
public LocalDate getItemAsDate(int idx) {
return Struct.objectToDate(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Time
*/
public LocalTime getItemAsTime(int idx) {
return Struct.objectToTime(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as String
*/
public String getItemAsString(int idx) {
return Struct.objectToString(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Binary
*/
public Memory getItemAsBinary(int idx) {
return Struct.objectToBinary(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as CompositeStruct
*/
public CompositeStruct getItemAsComposite(int idx) {
return Struct.objectToComposite(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as RecordStruct
*/
public RecordStruct getItemAsRecord(int idx) {
return Struct.objectToRecord(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as ListStruct
*/
public ListStruct getItemAsList(int idx) {
return Struct.objectToList(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Struct
*/
public Struct getItemAsStruct(int idx) {
return Struct.objectToStruct(this.getItem(idx));
}
/**
* Unlike getItem, this returns the value (inner) rather than struct wrapping
* the value.
*
* @param idx position in list of the item desired (0 based)
* @return field's "inner" value as Xml (will parse if value is string)
*/
public XElement getItemAsXml(int idx) {
return Struct.objectToXml(this.getItem(idx));
}
/**
* @param idx position in list of the item desired (0 based)
* @return true if item does not exist or if item is string and its value is empty
*/
public boolean isItemEmpty(int idx) {
if (idx >= this.items.size())
return true;
Object o = this.items.get(idx);
if (o == null)
return true;
if (o instanceof CharSequence)
return o.toString().isEmpty();
return false;
}
/**
* @return number of items in this list
*/
public int getSize() {
return this.items.size();
}
/**
* @param idx position in list of the item to remove from list
*/
public void removeItem(int idx) {
if (idx >= this.items.size())
return;
this.items.remove(idx);
}
/*
* @param idx position in list of the item to remove from list
*/
public void removeItem(Struct itm) {
// TODO dispose
//Struct old = this.items.get(itm);
//if (old != null)
// old.dispose();
this.items.remove(itm);
}
@Override
protected void doCopy(Struct n) {
super.doCopy(n);
ListStruct nn = (ListStruct)n;
nn.addCollection(this.items);
}
@Override
public Struct deepCopy() {
ListStruct cp = new ListStruct();
this.doCopy(cp);
return cp;
}
/**
*
* @return schema for the primary/default data type of the list items
*/
public DataType getChildType() {
if (this.explicitType != null)
return this.explicitType.getPrimaryItemType();
return null;
}
@Override
public void clear() {
this.items.clear();
}
@Override
public void operation(StackEntry stack, XElement code) {
if ("Set".equals(code.getName())) {
this.clear();
String json = stack.resolveValueToString(code.getText());
if (StringUtil.isNotEmpty(json)) {
ListStruct pjson = (ListStruct) CompositeParser.parseJson(" [ " + json + " ] ").getResult();
for (Struct s : pjson.getItems())
this.items.add(s);
}
// TODO else check for Xml or Yaml
stack.resume();
return;
}
else if ("AddItem".equals(code.getName())) {
Struct sref = stack.refFromElement(code, "Value");
this.addItem(sref);
stack.resume();
return;
}
else if ("RemoveItem".equals(code.getName())) {
long idx = stack.intFromElement(code, "Index", -1);
if (idx > -1)
this.removeItem((int) idx);
stack.resume();
return;
}
else if ("Clear".equals(code.getName())) {
this.clear();
stack.resume();
return;
}
super.operation(stack, code);
}
public List<String> toStringList() {
List<String> nlist = new ArrayList<>();
for (Struct s : this.items)
if (s != null)
nlist.add(s.toString());
return nlist;
}
public boolean contains(Struct v) {
return this.items.contains(v);
}
public List<Object> toObjectList() {
List<Object> nlist = new ArrayList<>();
for (Struct s : this.items)
if (s instanceof ScalarStruct)
nlist.add(((ScalarStruct)s).getGenericValue());
return nlist;
}
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
protected int cnt = 0;
@Override
public boolean hasNext() {
return (cnt < ListStruct.this.items.size());
}
@Override
public Object next() {
Object o = ListStruct.this.getAt(cnt);
this.cnt++;
return o;
}
};
}
}