All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.uvm.ccts.arden.model.AList Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
/*
 * Copyright 2015 The University of Vermont and State
 * Agricultural College, Vermont Oxford Network, and The University
 * of Vermont Medical Center.  All rights reserved.
 *
 * Written by Matthew B. Storer 
 *
 * This file is part of Arden Model.
 *
 * Arden Model is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Arden Model is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Arden Model.  If not, see .
 */

package edu.uvm.ccts.arden.model;

import edu.uvm.ccts.arden.util.ListUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import java.util.*;

/**
 * An Arden List object
 * 
* A list is an ordered set of elements, each of which may be null, Boolean, event, destination, message, term, number, * time, duration, or string. There are no nested lists; that is, a list cannot be the element of another list. Lists * may be heterogeneous; that is, the elements in a list may be of different types. There is one list constant, the * empty list, which is signified by using a pair of empty parentheses: (). White space is allowed within an empty * list's parentheses. Other lists are created by using list operators like the comma (,) to build lists from single * items (see Section 9.2). For the output format of lists (including single element lists), see Section 9.8. For * example, these are valid lists: *
    *
  • 4, 3, 5
  • *
  • 3, true, 5, null
  • *
  • ,1
  • *
  • ()
  • *
* If operators that expect list arguments are presented non-list arguments, the arguments are implicitly converted to * single-element lists before the operator is applied. * @see 8.8 */ public class AList extends ADataType implements Iterable, RowIterable { private List list = new ArrayList(); /** * Construct a new, empty Arden list. */ public AList() { } /** * Construct a new copy of an Arden list. The resulting list is a deep-copy of {@code list}. Primary times * of list items are preserved. * @param list */ public AList(AList list) { for (ADataType item : list.list) { this.list.add(item.copy()); } } public AList(List list) { this.list = list; } /** * Generates a deep-copy of this object * @return A deep-copy of this object. Primary times are preserved. */ @Override public AList copy() { return new AList(this); } @Override public String toString() { return "(" + StringUtils.join(list, ", ") + ")"; } /** * @return the number of elements in the list */ public int size() { return list.size(); } /** * Determines whether or not the list is empty * @return true if the list is empty; false otherwise */ public boolean isEmpty() { return list.isEmpty(); } public boolean contains(ADataType obj) { return list.contains(obj); } /** * Gets an item from the list by index. IMPORTANT NOTE: indexes are 1-based! * @param index * @return */ public ADataType get(int index) { if (index >= 1 && index <= list.size()) { return list.get(index - 1); } else { return new ANull(); } } /** * Sets an item in the list by index. IMPORTANT NOTE: indexes are 1-based! * @param index * @param obj */ public void set(int index, ADataType obj) { if (index >= 1 && index <= list.size()) { if (obj == null) { list.set(index - 1, new ANull()); } else if (obj instanceof AList) { remove(index); insert(obj, index); } else { list.set(index - 1, obj); } } } @Override public boolean hasPrimaryTime() { return getPrimaryTime() != null; } /** * Gets the primary time. * * This function operates by iterating through all elements in the list. This function will return a * primary time only if all elements have the same primary time. * * @return a {@link Time} object representing the primary time held by all list * elements; otherwise {@code null} is returned. */ @Override public Time getPrimaryTime() { return ListUtil.getPrimaryTime(list); } /** * Add a single Arden-object into this list * @param obj */ public void add(ADataType obj) { if (obj == null) { list.add(new ANull()); } else if (obj instanceof AList) { // nested lists are not permitted list.addAll(((AList) obj).list); } else { list.add(obj); } } /** * Adds all elements from list into this object's backing list * @param list */ public void addAll(AList list) { this.list.addAll(list.list); } /** * Inserts an object into the list at the specified position. Note that unlike Java, list indexes in Arden start * at 1, not 0. * @param obj an object to insert into the list. If {@code obj} is an {@link AList} itself, the contents of * {@code obj} are added to the list, starting at the specified position {@code pos}. * @param pos the position in the list at which to insert {@code obj}. Must be a number between 1 and * {@link #size}. */ public void insert(ADataType obj, int pos) { int index; if (pos <= 1) index = 0; else if (pos > list.size()) index = list.size(); else index = pos - 1; if (obj == null) { list.add(index, new ANull()); } else if (obj instanceof AList) { // nested lists are not permitted list.addAll(index, ((AList) obj).list); } else { list.add(index, obj); } } /** * Removes an item from the given position in the list. Note that unlike Java, list indexes in Arden start at 1, * not 0. * @param pos A number between 1 and {@link #size} */ public void remove(int pos) { if (pos >= 1 && pos <= list.size()) { list.remove(pos - 1); } } /** * @return the first element in the list, or {@link ANull} if the list is empty. */ public ADataType first() { if (list.size() > 0) { return list.get(0); } else { return new ANull(); } } /** * @param x The number of elements from the start of the list to return * @return the first {@code x} elements from the list, or an empty {@link AList} if the list is empty */ public AList first(int x) { // todo : ensure results are earliest in time if list is the result of a time-sorted query. see 9.14.4 return sublist(1, x); } /** * @return the last element in the list, or {@link ANull} if the list is empty. */ public ADataType last() { if (list.size() > 0) { return list.get(list.size() - 1); } else { return new ANull(); } } /** * @param x The number of elements from the end of the list to return * @return the last {@code x} elements from the list, or an empty {@link AList} if the list is empty */ public AList last(int x) { // todo : ensure results are latest in time if list is the result of a time-sorted query. see 9.14.5 return sublist(list.size() - x + 1, x); } /** * Returns the item from the list that has a primary time closest to the time specified. If the list contains * any items not having a primary time, or if the list is empty, {@code null} is returned. * @see 9.13.2 * @param time * @return */ public ADataType nearest(Time time) { if ( ! eachHasPrimaryTime() ) return null; Long minDiff = null; ADataType rval = null; for (ADataType obj : list) { long diff = Math.abs(time.subtract(obj.getPrimaryTime()).getSeconds()); if (minDiff == null || diff < minDiff) { minDiff = diff; rval = obj; // primary time should be maintained } } return rval; } public ANumber nearestIndex(Time time) { return indexOf(nearest(time)); // do not maintain primary } public ANumber earliestIndex() { AList list = earliest(1); if (list == null) return null; else if (list.isEmpty()) return null; // if list is empty, null is returned - see 9.12.22.2 else { ADataType obj = list.get(1); ANumber index = indexOf(obj); index.setPrimaryTime(obj.getPrimaryTime()); // primary time of the argument is maintained return index; } } public AList earliestIndex(int x) { AList list = earliest(x); if (list == null) return null; else if (list.isEmpty()) return list; else return indexOf(list); // do not maintain primary time } public ADataType earliest() { // todo : If there is a tie, then it selects the element with the lowest index. spec 9.12.17 AList list = earliest(1); if (list == null) return null; else if (list.isEmpty()) return null; // return null if list is empty - see 9.12.17 else return list.get(1); } public AList earliest(int x) { AList sorted = sortByTime(); if (sorted == null) return null; AList subList = sorted.sublist(1, x); AList list = new AList(); for (ADataType obj : this.list) { if (subList.contains(obj)) list.add(obj); // relies on equals(), which incorporates primaryTime } return list; } public ANumber latestIndex() { AList list = latest(1); if (list == null) return null; else if (list.isEmpty()) return null; // if list is empty, null is returned - see 9.12.22.1 else { ADataType obj = list.get(1); ANumber index = indexOf(obj); index.setPrimaryTime(obj.getPrimaryTime()); // primary time of the argument is maintained return index; } } public AList latestIndex(int x) { AList list = latest(x); if (list == null) return null; else if (list.isEmpty()) return list; else return indexOf(list); // do not maintain primary time } public ADataType latest() { // todo : If there is a tie, then it selects the element with the lowest index. spec 9.12.16 AList list = latest(1); if (list == null) return null; else if (list.isEmpty()) return null; // return null if list is empty - see 9.12.16 else return list.get(1); } public AList latest(int x) { AList sorted = sortByTime(); if (sorted == null) return null; AList subList = sorted.sublist(this.list.size() - x + 1, x); AList list = new AList(); for (ADataType obj : this.list) { if (subList.contains(obj)) list.add(obj); // relies on equals(), which incorporates primaryTime } return list; } /** * Compares this object to another as an identity comparison, in which the primary time of the objects are * considered. * @param o * @return {@code true} if {@code this} and {@code o} have the same value and the same primary * time. */ @Override public boolean equals(Object o) { if (o == null) return false; if (o == this) return true; if (o.getClass() != getClass()) return false; AList al = (AList) o; return new EqualsBuilder() .append(list, al.list) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(59, 163) .append(list) .toHashCode(); } /** * Compares this object to another, considering only the value of the object, and not the primary time. * @param o * @return {@code true} if {@code this} and {@code o} have the same value. */ @Override public boolean hasValue(ADataType o) { if (o == null) return false; if (o == this) return true; if (o.getClass() != getClass()) return false; AList al = (AList) o; if (list.size() != al.list.size()) return false; for (int i = 0; i <= list.size(); i ++) { if ( ! list.get(i).hasValue(al.list.get(i)) ) { return false; } } return true; } @Override public Iterator iterator() { return list.iterator(); } @Override public Object toJavaObject() { List list = new ArrayList(); for (ADataType adt : this.list) { list.add(adt.toJavaObject()); } return list; } /** * Determines the list type. Useful for identifying the contents of a list. * @return a {@link ListType} that represents the contents of the list. */ public ListType getListType() { if (list.isEmpty()) { return ListType.EMPTY; } else { ListType t = ListType.NULL; for (ADataType obj : list) { if (obj instanceof ANumber && (t == ListType.NULL || t == ListType.NUMBER)) t = ListType.NUMBER; else if (obj instanceof AString && (t == ListType.NULL || t == ListType.STRING)) t = ListType.STRING; else if (obj instanceof ABoolean && (t == ListType.NULL || t == ListType.BOOLEAN)) t = ListType.BOOLEAN; else if (obj instanceof Time && (t == ListType.NULL || t == ListType.TIME)) t = ListType.TIME; else if (obj instanceof TimeOfDay && (t == ListType.NULL || t == ListType.TIME_OF_DAY)) t = ListType.TIME_OF_DAY; else if (obj instanceof Duration && (t == ListType.NULL || t == ListType.DURATION)) t = ListType.DURATION; else return ListType.MIXED; } return t; } } /** * Only refers to sortable by data, not sortable by time. All Arden objects are sortable by primary time; not all * are sortable by data value. * @return {@code true} if the list contains a homogeneous */ public boolean isSortable() { ListType type = getListType(); if (type == ListType.MIXED || type == ListType.EMPTY) return false; // not mixed or empty, must be all of same class return list.get(0).isComparable(); } public AList whereTimeIsPresent() { AList filtered = new AList(); for (ADataType obj : list) { if (obj.hasPrimaryTime()) filtered.add(obj.copy()); } return filtered; } public AList sortByTime() { for (ADataType obj : list) { if ( ! obj.hasPrimaryTime() ) return null; } AList sorted = copy(); Collections.sort(sorted.list, new PrimaryTimeComparator()); return sorted; } public AList sort() { return sortByData(); } /** * Generates a sorted copy of this list based on data values. All list elements must be of the same type, and must * implement {@link Comparable}. * @return a sorted copy of this list, or {@code null} if the list cannot be sorted. */ public AList sortByData() { if (isSortable()) { try { AList sorted = copy(); Collections.sort(sorted.list, new DataComparator()); return sorted; } catch (Exception e) { // handle silently } } return null; } public AList reverse() { AList reversed = copy(); Collections.reverse(reversed.list); return reversed; } /** * @param startAt * @param numElements * @return the selected elements from this list, or an empty list if the list is empty */ public AList sublist(int startAt, int numElements) { if (startAt >= 1 && startAt <= list.size() && numElements != 0) { int fromIndex; int toIndex; if (numElements > 0) { fromIndex = startAt - 1; toIndex = fromIndex + numElements > list.size() ? list.size() : fromIndex + numElements; } else { // if numElements < 0 toIndex = startAt; fromIndex = toIndex + numElements < 0 ? 0 : toIndex + numElements; } return new AList(list.subList(fromIndex, toIndex)); } else { return new AList(); } } public ANumber minimumIndex() { AList minList = minimum(1); if (minList == null) return null; else if (minList.isEmpty()) return null; // if list is empty, null is returned - see 9.12.22.3 else return indexOf(minList.get(1)); } public AList minimumIndex(int x) { AList minList = minimum(x); if (minList == null) return null; else if (minList.isEmpty()) return minList; else return indexOf(minList); } public ADataType minimum() { AList minList = minimum(1); if (minList == null) return null; else if (minList.isEmpty()) return null; // if list is empty, null is returned - see 9.12.9 else return minList.get(1); } /** * Finds the {@code x} minimum elements from this list. Elements are returned in the order in which they appear * in the source list. Source list must be homogeneous and contain comparable elements. Primary times are * maintained. * @param x * @return */ public AList minimum(int x) { // todo : If there is a tie, then it selects the element with the latest primary time. ADataType sorted = sort(); if (sorted == null) return null; AList subList = ((AList) sorted).sublist(1, x); AList list = new AList(); for (ADataType obj : this.list) { if (subList.contains(obj)) list.add(obj); } return list; } public ANumber maximumIndex() { AList list = maximum(1); if (list == null) return null; else if (list.isEmpty()) return null; // if list is empty, null is returned - see 9.12.22.4 else return indexOf(list.get(1)); } public AList maximumIndex(int x) { AList list = maximum(x); if (list == null) return null; else if (list.isEmpty()) return list; else return indexOf(list); } public ADataType maximum() { AList list = maximum(1); if (list == null) return null; else if (list.isEmpty()) return null; // if list is empty, null is returned - see 9.12.10 else return list.get(1); } /** * Finds the {@code x} maximum elements from this list. Elements are returned in the order in which they appear * in the source list. Source list must be homogeneous and contain comparable elements. Primary times are * maintained. * @param x * @return */ public AList maximum(int x) { // todo : If there is a tie, then it selects the element with the latest primary time. ADataType sorted = sort(); if (sorted == null) return null; AList subList = ((AList) sorted).sublist(this.list.size() - x + 1, x); AList list = new AList(); for (ADataType obj : this.list) { if (subList.contains(obj)) list.add(obj); } return list; } public AList increase() { AList list = new AList(); // The primary time of the second item in each successive pair is kept. if (this.list.size() > 1) { ListType type = getListType(); if (type == ListType.NUMBER) { for (int i = 1; i < this.list.size(); i ++) { ANumber first = (ANumber) this.list.get(i - 1); ANumber second = (ANumber) this.list.get(i); ANumber n = second.subtract(first); n.setPrimaryTime(second.getPrimaryTime()); list.add(n); } } else if (type == ListType.TIME) { for (int i = 1; i < this.list.size(); i ++) { Time first = (Time) this.list.get(i - 1); Time second = (Time) this.list.get(i); Duration d = second.subtract(first); d.setPrimaryTime(second.getPrimaryTime()); list.add(d); } } else if (type == ListType.DURATION) { for (int i = 1; i < this.list.size(); i ++) { Duration first = (Duration) this.list.get(i - 1); Duration second = (Duration) this.list.get(i); Duration d = second.subtract(first); d.setPrimaryTime(second.getPrimaryTime()); list.add(d); } } } return list; } public AList increasePct() { return new AList(increaseDecreasePct(true)); } public AList decrease() { AList list = new AList(); // The primary time of the second item in each successive pair is kept. if (this.list.size() > 1) { ListType type = getListType(); if (type == ListType.NUMBER) { for (int i = 1; i < this.list.size(); i ++) { ANumber first = (ANumber) this.list.get(i - 1); ANumber second = (ANumber) this.list.get(i); ANumber n = first.subtract(second); n.setPrimaryTime(second.getPrimaryTime()); list.add(n); } } else if (type == ListType.TIME) { for (int i = 1; i < this.list.size(); i ++) { Time first = (Time) this.list.get(i - 1); Time second = (Time) this.list.get(i); Duration d = first.subtract(second); d.setPrimaryTime(second.getPrimaryTime()); list.add(d); } } else if (type == ListType.DURATION) { for (int i = 1; i < this.list.size(); i ++) { Duration first = (Duration) this.list.get(i - 1); Duration second = (Duration) this.list.get(i); Duration d = first.subtract(second); d.setPrimaryTime(second.getPrimaryTime()); list.add(d); } } } return list; } public AList decreasePct() { return new AList(increaseDecreasePct(false)); } public AList interval() { if ( ! eachHasPrimaryTime() ) return null; AList intervalList = new AList(); Time t = null; for (ADataType obj : list) { if (t == null) { t = obj.getPrimaryTime(); } else { intervalList.add(obj.getPrimaryTime().subtract(t)); t = obj.getPrimaryTime(); } } return intervalList; } public boolean eachHasPrimaryTime() { for (ADataType obj : list) { if ( ! obj.hasPrimaryTime() ) return false; } return true; } public AList getAllIndexesFor(ADataType test) { AList indexList = new AList(); for (int i = 0; i < list.size(); i ++) { if (list.get(i).hasValue(test)) { // intentionally using hasValue instead of equals - pretty sure indexList.add(new ANumber(i + 1)); // we want to ignore primary times for this comparison } } return indexList; } ////////////////////////////////////////////////////////////////////////////////// // Private methods // private AList indexOf(AList objList) { if (objList == null) return null; AList indexList = new AList(); int startIndex = 0; for (ADataType obj : objList) { for (int i = startIndex; i < list.size(); i ++) { if (list.get(i).equals(obj)) { indexList.add(new ANumber(i + 1)); startIndex = i + 1; break; } } } return indexList; } private ANumber indexOf(ADataType obj) { if (obj == null) return null; for (int i = 0; i < list.size(); i ++) { if (list.get(i).equals(obj)) return new ANumber(i + 1); } return null; } private List increaseDecreasePct(boolean increase) { List list = new ArrayList(); // The primary time of the second item in each successive pair is kept. if (this.list.size() > 1) { ListType type = getListType(); if (type == ListType.NUMBER) { for (int i = 1; i < this.list.size(); i ++) { ANumber first = (ANumber) this.list.get(i - 1); ANumber second = (ANumber) this.list.get(i); ANumber n = second.divide(first).subtract(1).multiply(100); n.setPrimaryTime(second.getPrimaryTime()); if (increase) list.add(n); else list.add(n.multiply(-1)); } } else if (type == ListType.DURATION) { for (int i = 1; i < this.list.size(); i ++) { Duration first = ((Duration) this.list.get(i - 1)); Duration second = ((Duration) this.list.get(i)); ANumber n = new ANumber((((double) second.getSeconds() / first.getSeconds()) - 1) * 100); n.setPrimaryTime(second.getPrimaryTime()); if (increase) list.add(n); else list.add(n.multiply(-1)); } } } return list; } @Override public int getColCount() { return 1; } @Override public int getRowCount() { return list.size(); } @Override public List getRow(int index) { List adtList = new ArrayList(); adtList.add(list.get(index)); return adtList; } private static final class PrimaryTimeComparator implements Comparator { @Override public int compare(ADataType o1, ADataType o2) { Time t1 = o1.getPrimaryTime(); Time t2 = o2.getPrimaryTime(); if (t1 == null) return -1; else if (t2 == null) return 1; else return t1.compareTo(t2); } } @SuppressWarnings("unchecked") private static final class DataComparator implements Comparator { @Override public int compare(ADataType o1, ADataType o2) { if (o1 == null) return -1; else if (o2 == null) return 1; else return ((Comparable) o1).compareTo(o2); } } }