/*
* Copyright (C) 2000 - 2008 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD 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 OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://www.openbluedragon.org/
*/
package com.naryx.tagfusion.cfm.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import com.naryx.tagfusion.cfm.parser.script.userDefinedFunction;
import com.naryx.tagfusion.cfm.tag.cfDUMP;
/**
* The <B>cfArrayData</B> class represents a tagServlet array. As a tagServlet array can
* dynamically increase its size, a Vector is used to hold the cfDatas that make up the
* elements rather than an array. Note multi-dimensional arrays are not supported.
*/
public abstract class cfArrayData extends cfArrayDataBase implements java.io.Serializable{
static final long serialVersionUID = 1;
/** The vector that represents the tagServlet array. */
protected Vector<cfData> data;
protected int dimensions;
protected cfArrayData() {
// purely for subclasses
}
protected cfArrayData( int _dimensions ) {
super();
data = new Vector<cfData>();
setInstance( this );
dimensions = _dimensions;
if ( dimensions < 0 ) dimensions = 1;
}
protected cfArrayData( int _dimensions, Vector<? extends cfData> _data ) {
super();
data = new Vector<cfData>( _data );
setInstance( this );
dimensions = _dimensions;
}
/**
* Creates a new instance of cfArrayData setting the number of dimension the
* array will have.
*
* @param _dimension an int between one and three.
*/
public static cfArrayData createArray( int _dimensions ){
return new cfArrayListData( _dimensions );
}
/**
* This is called from a Java object. The only caveat is that if we bring back a string array
* we will convert it to a proper array and let it modified
*
* @param _arr
* @return
*/
public static cfArrayData createArray( Object _arr ){
try{
if ( _arr instanceof String[] ){
String[] sarr = (String[])_arr;
cfArrayListData arr = new cfArrayListData(1);
for ( int x=0; x < sarr.length; x++ )
arr.addElement( new cfStringData(sarr[x]) );
return arr;
}
}catch(cfmRunTimeException ignoreWeCouldNotCreate){}
return new cfFixedArrayData( _arr );
}
// creates a shallow copy of the given array. The parent query
// fields, if set, are not copied to the resulting array
public static cfArrayData createFrom( cfArrayData _arr ){
cfArrayListData newArr = new cfArrayListData( _arr.dimensions );
newArr.data = _arr.data;
newArr.instance = newArr;
return newArr;
}
public byte getDataType(){ return cfData.CFARRAYDATA; }
public String getDataTypeName() { return "array"; }
public int getDimension(){ return dimensions; }
public int size() {
return data.size();
}
public boolean isEmpty() {
return data.isEmpty();
}
public cfData getData( cfData arrayIndex ) throws cfmRunTimeException {
int indx;
try{
indx = arrayIndex.getInt();
}catch(Exception E){
cfCatchData catchData = new cfCatchData();
catchData.setType( cfCatchData.TYPE_APPLICATION );
catchData.setMessage( "Attempted to access array with invalid array index." );
throw new cfmRunTimeException( catchData );
}
return getData( indx );
}
public cfData getData( int indx ) throws cfmRunTimeException {
if ( indx > data.size() )
data.setSize( indx );
else if ( indx < 1 ){
cfCatchData catchData = new cfCatchData();
catchData.setType( cfCatchData.TYPE_APPLICATION );
catchData.setMessage( "Attempted to access array with invalid array index [" + indx + "]." );
throw new cfmRunTimeException( catchData );
}
return (cfData)data.get( indx-1 );
}
public void setData( cfData arrayIndex, cfData _data ) throws cfmRunTimeException{
int indx;
try{
indx = arrayIndex.getInt();
}catch(Exception E){
cfCatchData catchData = new cfCatchData();
catchData.setType( cfCatchData.TYPE_APPLICATION );
catchData.setMessage( "Attempted to access array with invalid array index." );
throw new cfmRunTimeException( catchData );
}
setData( indx, _data );
}
public void setData( int _index, cfData _element ) throws cfmRunTimeException {
if ( _index > data.size() )
data.setSize( _index );
data.set( _index - 1, _element );
}
public void addElement( cfData _element ) throws cfmRunTimeException {
data.add( _element );
}
public void addElementAt( cfData _element, int _index ) throws cfmRunTimeException {
data.add( ( _index - 1 ), _element );
}
public void removeAllElements() throws cfmRunTimeException{
data.clear();
}
public void removeElementAt( int _no ) throws cfmRunTimeException {
data.remove( ( _no - 1 ) );
}
public void setCapacity( int _size ){
data.setSize( _size );
}
public void setElements( int _start, int _end, cfData _value ) throws cfmRunTimeException{
for ( int x = ( _start - 1 ); x < _end ; x++ ){
data.set( x, _value );
}
}
public void elementSwap( int _start, int _end ) throws cfmRunTimeException {
cfData first = data.get( _start - 1 );
cfData second = data.get( _end - 1 );
data.set( _end - 1, first );
data.set( _start - 1, second );
}
public void replace(int x, cfData _value) {
data.set( x-1, _value);
}
@SuppressWarnings("unchecked")
public cfArrayData copy() {
cfArrayData arr = createArray( dimensions );
arr.data = (Vector<cfData>)data.clone(); // unchecked cast is OK here
for ( int i = 0; i < arr.data.size(); i++ ) {
cfData nextData = arr.data.get( i );
if ( nextData != null && nextData.getDataType() == cfData.CFARRAYDATA ) {
arr.data.set( i, ( (cfArrayData)nextData ).copy() );
}
}
return arr;
}
public cfData duplicate(){
return duplicate( true );
}
public cfData duplicate( boolean _deepCopy ){
cfArrayData arrCopy = copy();
Vector<cfData> theData = arrCopy.data;
Vector<cfData> clonedArrData = new Vector<cfData>();
cfData clonedData;
Object nextElement = null;
int arrLen = theData.size();
for ( int i = 0; i < arrLen; i++ ){
nextElement = theData.get( i );
if ( nextElement != null ){
if ( _deepCopy || ( (cfData) nextElement ).getDataType() == cfData.CFARRAYDATA ){
clonedData = ( (cfData) nextElement ).duplicate();
}else{
clonedData = ( (cfData) nextElement );
}
if ( clonedData == null ){
return null;
}
clonedArrData.add( clonedData );
}else{
clonedArrData.add( null );
}
}
arrCopy.data = clonedArrData;
return arrCopy;
}
public double getMax()throws dataNotSupportedException{
if ( data.size() == 0 ){
return 0.0;
}
double max = getCfDataElement(0).getDouble();
double temp;
for ( int x = 1; x < data.size(); x++ ){
temp = getCfDataElement(x).getDouble();
if ( temp > max )
max = temp;
}
return max;
}
public double getMin()throws dataNotSupportedException{
if ( data.size() == 0 ){
return 0.0;
}
double min = getCfDataElement(0).getDouble();
double temp;
for ( int x = 1; x < data.size(); x++ ){
temp = getCfDataElement(x).getDouble();
if ( temp < min )
min = temp;
}
return min;
}
public double getAverage()throws dataNotSupportedException{
if ( data.size() == 0 )
return 0;
double sum = 0;
for ( int x = 0; x < data.size(); x++ )
sum += getCfDataElement(x).getDouble();
return ( sum / data.size() );
}
public double getSum()throws dataNotSupportedException{
int size = data.size();
double sum = 0;
for ( int x = 0; x < size; x++ )
sum += getCfDataElement(x).getDouble();
return sum;
}
public cfData getElement( int _index ){
return (cfData)data.get( ( _index - 1 ) );
}
// returns a cfData from the array from the given index.
// @throws dataNotSupportedException if the element at the provided index is null.
private cfData getCfDataElement( int _index ) throws dataNotSupportedException{
Object element = data.get(_index);
if ( element != null )
return (cfData) element;
else{
throw new dataNotSupportedException( "The value of array element [" + (_index+1) + "] is undefined." );
}
}
public String toString(){ return "{ARRAY:" + data + "}"; }
/**
* Decides what type of sort to carry out on this cfArrayData.
*
* @param _type the kind of sort to carry out, numeric, text or textnocase.
* @param _order the order to do the sort, asc or desc.
* @exception dataNotSupportedException when the cfData does not implement this method.
*/
public void sortArray( String _type, String _order ) throws dataNotSupportedException{
if ( _type.equalsIgnoreCase("numeric") )
sortNumeric( _order );
else if ( _type.equalsIgnoreCase("text") )
sortText( _order );
else if ( _type.equalsIgnoreCase("textnocase") )
sortTextNoCase( _order );
else
throw new dataNotSupportedException();
}
private void sortNumeric( String _order ) {
if ( _order == null || _order.equalsIgnoreCase("asc") ){
Collections.sort( data, new Comparator<cfData>(){
public int compare( cfData o1, cfData o2 ){
try{
if ( o1.getDouble() < o2.getDouble() )
return -1;
else
return 1;
} catch (dataNotSupportedException E){ return 0; }
}
} );
}else{
Collections.sort( data, new Comparator<cfData>(){
public int compare( cfData o1, cfData o2 ){
try{
if ( o1.getDouble() > o2.getDouble() )
return -1;
else
return 1;
} catch (dataNotSupportedException E){ return 0; }
}
} );
}
}
private void sortText( String _order ) {
if ( _order == null || _order.equalsIgnoreCase("asc") ){
Collections.sort( data, new Comparator<cfData>(){
public int compare( cfData o1, cfData o2 ){
try{
return o1.getString().compareTo( o2.getString() );
} catch (dataNotSupportedException E){ return 0; }
}
} );
}else{
Collections.sort( data, new Comparator<cfData>(){
public int compare( cfData o1, cfData o2 ){
try{
return o2.getString().compareTo( o1.getString() );
} catch (dataNotSupportedException E){ return 0; }
}
} );
}
}
private void sortTextNoCase( String _order ) {
if ( _order == null || _order.equalsIgnoreCase("asc") )
{
Collections.sort( data, new Comparator<cfData>()
{
public int compare( cfData o1, cfData o2 )
{
try
{
return o1.getString().toLowerCase().compareTo( o2.getString().toLowerCase() );
}
catch (dataNotSupportedException E){ return 0; }
}
} );
}
else //desc
{
Collections.reverse(data); //to fix bug #1177
Collections.sort( data, new Comparator<cfData>()
{
public int compare( cfData o1, cfData o2 )
{
try
{
return o2.getString().toLowerCase().compareTo( o1.getString().toLowerCase() );
} catch (dataNotSupportedException E){ return 0; }
}
} );
}
}
public String createList( String delimiter ) throws dataNotSupportedException{
return createList( delimiter, null );
}
public String createList( String delimiter, String qualifier ) throws dataNotSupportedException{
StringBuilder list = new StringBuilder();
Iterator<cfData> iter = data.iterator();
while ( iter.hasNext() ){
cfData next = iter.next();
if ( next != null ) {
String nextStr = next.getString();
if ( qualifier != null && nextStr.contains( delimiter ) ){
nextStr = qualifier + nextStr + qualifier;
}
list.append( nextStr );
}
list.append( delimiter );
}
return ( data.size() > 0 ? list.toString().substring( 0, list.length()-delimiter.length() ) : "" );
}
public void dump( java.io.PrintWriter out ){
dump( out, "", cfDUMP.TOP_DEFAULT );
}
public void dump( java.io.PrintWriter out, String _label, int _top ){
out.write( "<table class='cfdump_table_array'>" );
if ( data.size() > 0 ) {
out.write( "<th class='cfdump_th_array' colspan='2'>" );
if ( _label.length() > 0 ) out.write( _label + " - " );
out.write( "array</th>" );
int max = ( _top < data.size() ? _top : data.size() );
for ( int x=0; x < max; x++ ){
out.write( "<tr><td class='cfdump_td_array'>" );
out.write( (x+1) + "" );
out.write( "</td><td class='cfdump_td_value'>" );
cfData element = (cfData)data.get( x );
if ( ( element == null ) || ( element.getDataType() == cfData.CFNULLDATA ) )
out.write( "[undefined array element]" );
else
element.dump(out,"",_top);
out.write( "</td></tr>" );
}
} else {
out.write( "<th class='cfdump_th_array' colspan='2'>array [empty]</th>" );
}
out.write( "</table>" );
}
public void dumpWDDX( int version, java.io.PrintWriter out ){
if ( version > 10 )
out.write( "<a l='" );
else
out.write( "<array length='" );
out.write( data.size() + "" );
out.write( "'>" );
for ( int x=0; x < data.size(); x++ ){
if( data.get( x ) != null)
((cfData)data.get( x )).dumpWDDX( version, out );
}
if ( version > 10 )
out.write( "</a>" );
else
out.write( "</array>" );
}
// Comparison and hashing
/**
* Compares the specified object with this list for equality. Returns
* <tt>true</tt> if and only if the specified object is also a list, both
* lists have the same size, and all corresponding pairs of elements in
* the two lists are <i>equal</i>. (Two elements <tt>e1</tt> and
* <tt>e2</tt> are <i>equal</i> if <tt>(e1==null ? e2==null :
* e1.equals(e2))</tt>.) In other words, two lists are defined to be
* equal if they contain the same elements in the same order. This
* definition ensures that the equals method works properly across
* different implementations of the <tt>List</tt> interface.
*
* @param o the object to be compared for equality with this list.
* @return <tt>true</tt> if the specified object is equal to this list.
*/
public boolean equals( Object o ){
if ( o instanceof cfArrayData )
return data.equals( ((cfArrayData)o).data );
return false;
}
public boolean equals( cfData o ){
if ( o.getDataType() == cfData.CFARRAYDATA )
return data.equals( ((cfArrayData)o).data );
return false;
}
/**
* Returns the hash code value for this list. The hash code of a list
* is defined to be the result of the following calculation:
* <pre>
* hashCode = 1;
* Iterator i = list.iterator();
* while (i.hasNext()) {
* Object obj = i.next();
* hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
* }
* </pre>
* This ensures that <tt>list1.equals(list2)</tt> implies that
* <tt>list1.hashCode()==list2.hashCode()</tt> for any two lists,
* <tt>list1</tt> and <tt>list2</tt>, as required by the general
* contract of <tt>Object.hashCode</tt>.
*
* @return the hash code value for this list.
* @see Object#hashCode()
* @see Object#equals(Object)
* @see #equals(Object)
*/
public int hashCode() {
return data.hashCode();
}
/**
* Special function for looping around the data with a UserDefinedFunction
*
* @param SessionData
* @param _data
* @throws cfmRunTimeException
*/
public void each( cfDataSession SessionData, cfData _data ) throws cfmRunTimeException {
if ( _data.getDataType() != cfData.CFUDFDATA )
throw new cfmRunTimeException( catchDataFactory.generalException("Invalid Attribute", "Must be a user defined function") );
userDefinedFunction udf = (userDefinedFunction)_data;
List<cfData> args = new ArrayList<cfData>(1);
for ( int x = 0; x < data.size(); x++ ){
args.clear();
args.add( data.get(x) );
udf.execute( SessionData.Session, args );
}
}
}