package org.rosuda.JRI;
// JRclient library - client interface to Rserve, see http://www.rosuda.org/Rserve/
// Copyright (C) 2004 Simon Urbanek
// --- for licensing information see LICENSE file in the original JRclient distribution ---
import java.util.*;
/** implementation of R-lists<br>
This is rather preliminary and may change in future since it's not really proper.
The point is that the parser tries to interpret lists to be of the form entry=value,
where entry is stored in the "head" part, and value is stored in the "body" part.
Then using {@link #at(String)} it is possible to fetch "body" for a specific "head".
The terminology used is partly from hash fields - "keys" are the elements in "head"
and values are in "body" (see {@link #keys}).
<p>
On the other hand, R uses lists to store complex internal structures, which are not
parsed according to the structure - in that case "head" and "body" have to be evaluated
separately according to their meaning in that context.
@version $Id: RList.java 2720 2007-03-15 17:35:42Z urbanek $
*/
public class RList extends Object {
/** xpressions containing head, body and tag.
The terminology is a bit misleading (for historical reasons) - head corresponds to CAR, body to CDR and finally tag is TAG. */
public REXP head, body, tag;
/** cached keys (from TAG) */
String[] keys = null;
/** cached values(from CAR) */
REXP[] values = null;
/** flag denoting whether we need to re-fetch the cached values.<p><b.Note:</b> the current assumption is that the contents don't change after first retrieval - there is currently no recursive check! */
boolean dirtyCache = true;
/** constructs an empty list */
public RList() { head=body=tag=null; }
/** fake constructor to keep compatibility with Rserve (for now, will be gone soon) */
public RList(RVector v) {
Vector n = v.getNames();
if (n != null) {
keys = new String[n.size()];
n.copyInto(keys);
}
values=new REXP[v.size()];
v.copyInto(values);
dirtyCache=false;
// head,tail,tag are all invalid!
}
/** constructs an initialized list
@param h head xpression
@param b body xpression */
public RList(REXP h, REXP b) { head=h; body=b; tag=null; }
/** constructs an initialized list
@param h head xpression (CAR)
@param t tag xpression (TAG)
@param b body/tail xpression (CDR)
*/
public RList(REXP h, REXP t, REXP b) { head=h; body=b; tag=t; }
/** get head xpression (CAR)
@return head xpression */
public REXP getHead() { return head; }
/** get body xpression (CDR)
@return body xpression */
public REXP getBody() { return body; }
/** get tag xpression
@return tag xpression */
public REXP getTag() { return tag; }
/** internal function that updates cached vectors
@return <code>true</code> if the conversion was successful */
boolean updateVec() {
if (!dirtyCache) return true;
// we do NOT run it recursively, because in most cases only once instance is asked
RList cur = this;
int l = 0;
while (cur!=null) {
l++;
REXP bd = cur.getBody();
cur = (bd==null)?null:bd.asList();
}
keys=new String[l];
values=new REXP[l];
cur = this;
l=0;
while (cur != null) {
REXP x = cur.getTag();
if (x!=null) keys[l]=x.asSymbolName();
values[l] = cur.getHead();
REXP bd = cur.getBody();
cur = (bd==null)?null:bd.asList();
l++;
}
dirtyCache=false;
return true;
}
/** get xpression given a key
@param v key
@return xpression which corresponds to the given key or
<code>null</code> if list is not standartized or key not found */
public REXP at(String v) {
if (!updateVec() || keys==null || values==null) return null;
int i=0;
while (i<keys.length) {
if (keys[i].compareTo(v)==0) return values[i];
i++;
}
return null;
}
/** get element at the specified position
@param i index
@return xpression at the index or <code>null</code> if list is not standartized or
if index out of bounds */
public REXP at(int i) {
return (!updateVec() || values==null || i<0 || i>=values.length)?null:values[i];
}
/** returns all keys of the list
@return array containing all keys or <code>null</code> if list is not standartized */
public String[] keys() {
return (!updateVec())?null:this.keys;
}
}