/*
* Copyright (C) 2000 - 2012 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://openbd.org/
* $Id: $
*/
package com.naryx.tagfusion.cfm.engine;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.nary.util.CaseSensitiveMap;
import com.nary.util.FastMap;
import com.nary.util.HashMap;
import com.nary.util.SequencedHashMap;
import com.naryx.tagfusion.cfm.parser.script.userDefinedFunction;
import com.naryx.tagfusion.cfm.tag.cfDUMP;
import com.naryx.tagfusion.cfm.tag.tagUtils;
/**
* This class implements the CFML data structure
*/
@SuppressWarnings("deprecation")
public class cfStructData extends cfStructDataBase implements Map, java.io.Serializable {
/*************************************************************************************
* IMPORTANT - PLEASE READ
*
* Synchronization. The internal representation of a CFML struct is now based
* on java.util.Map instead of java.util.Hashtable. A key difference is that
* Hashtable is synchronized (thread-safe) but Map is not. Instead we've
* synchronized the methods that access the Map.
*
* But, that's not all there is to it. Whenever iterating over a Map (whether
* in this class or a subclass), you *MUST* manually synchronize on the Map
* object. Use this code as a prototype:
*
* Map hashdata = getHashData(); // only subclasses need to do this
* synchronized ( hashdata ) { Iterator iter = hashdata.keySet().iterator();
* while ( iter.hasNext() ) { String key = (String)iter.next(); cfData val =
* (cfData)hashdata.get( key ); } }
*
* The safe way for a class that is not a subclass of cfStructData to iterate
* through the elements is to use the keys() method and loop over the keys:
*
* Object[] keys = struct.keys(); for ( int i = 0; i < keys.length; i++ ) {
* String key = (String)keys[ i ]; cfData val = struct.getData( key ); }
*
*************************************************************************************/
static final long serialVersionUID = 1;
protected boolean isBDAdminStruct = false;
public cfStructData() {
this(FastMap.CASE_INSENSITIVE);
}
public cfStructData(boolean caseSensitive) {
// what about subclasses?
this(new FastMap<String, cfData>(caseSensitive));
}
public cfStructData(Map<String, cfData> _hashdata) {
setHashData(_hashdata);
setInstance(this);
}
public byte getDataType() {
return cfData.CFSTRUCTDATA;
}
public String getDataTypeName() {
return "struct";
}
public boolean isStruct() {
return true;
}
/**************************************************************************
* SUBCLASSES: the following methods contain references to the private
* hashdata data store and MUST be overridden by subclasses that use an
* alternate data store.
**************************************************************************/
protected Map<String, cfData> getHashData() {
return hashdata;
}
protected void setHashData(Map<String, cfData> _hashdata) {
hashdata = _hashdata;
setInstance(hashdata);
}
public boolean isCaseSensitive() {
if (hashdata instanceof CaseSensitiveMap) {
return ((CaseSensitiveMap<String, cfData>) hashdata).isCaseSensitive();
} else {
throw new UnsupportedOperationException(); // return true?
}
}
public synchronized cfData getData(String _key) {
return hashdata.get(_key);
}
public synchronized void setData(String _key, cfData _data) {
hashdata.put(_key, _data);
}
public synchronized void setData(String _key, String _data) {
hashdata.put(_key, new cfStringData(_data) );
}
public synchronized void setData(String _key, int _data) {
hashdata.put(_key, new cfNumberData(_data) );
}
public synchronized void setData(String _key, long _data) {
hashdata.put(_key, new cfNumberData(_data) );
}
public synchronized void setData(String _key, Date _data) {
hashdata.put(_key, new cfDateData(_data) );
}
public synchronized void deleteData(String _key) throws cfmRunTimeException {
hashdata.remove(_key);
}
public synchronized boolean containsKey(String _key) {
return hashdata.containsKey(_key);
}
public synchronized boolean containsValue(cfData _data) {
return hashdata.containsValue(_data);
}
public synchronized Object[] keys() {
return hashdata.keySet().toArray();
}
// Map interface method
public synchronized void clear() {
hashdata.clear();
}
// Map interface method
public int size() {
return hashdata.size();
}
// Map interface method
public boolean isEmpty() {
return hashdata.isEmpty();
}
// Map interface method
public synchronized Set<String> keySet() {
return hashdata.keySet();
}
// Map interface method
public synchronized boolean equals(Object o) {
if (o instanceof cfStructData)
return hashdata.equals(((cfStructData) o).hashdata);
return false;
}
public synchronized boolean equals(cfData o) {
if (o.getDataType() == cfData.CFSTRUCTDATA)
return hashdata.equals(((cfStructData) o).hashdata);
return false;
}
// Map interface method
public synchronized int hashCode() {
return hashdata.hashCode();
}
// create a shallow copy
protected Map<String, cfData> cloneHashdata() {
if (hashdata instanceof FastMap) {
return new FastMap((FastMap) hashdata);
} else if (hashdata instanceof HashMap) {
return new HashMap(hashdata, ((HashMap) hashdata).isCaseSensitive());
} else if (hashdata instanceof SequencedHashMap) { // arguments, see bug
// #3226
return new SequencedHashMap(hashdata, ((SequencedHashMap) hashdata).isCaseSensitive());
} else {
throw new UnsupportedOperationException();
}
}
/**************************************************************************
* The following methods do not reference the private hashdata attribute. They
* do not need to be overridden by subclasses that use an alternate data store
**************************************************************************/
public cfData getData(cfData arrayIndex) throws cfmRunTimeException {
return getData(arrayIndex.getString());
}
public void setData(cfData _key, cfData _data) throws cfmRunTimeException {
setData(_key.getString(), _data);
}
public cfData removeData(String _key) throws cfmRunTimeException {
cfData data = getData(_key);
if (data != null) {
deleteData(_key);
}
return data;
}
public cfArrayData getKeyArray() throws cfmRunTimeException {
cfArrayData array = cfArrayData.createArray(1);
Object[] keys = keys();
for (int i = 0; i < keys.length; i++) {
array.addElement(new cfStringData((String) keys[i]));
}
return array;
}
public synchronized Object clone() {
// Creates a shallow copy.
return new cfStructData(cloneHashdata());
}
public synchronized Map<String, cfData> copy() {
Map<String, cfData> copy = cloneHashdata();
Object[] keys = keys();
for (int i = 0; i < keys.length; i++) {
String key = (String) keys[i];
Object val = getData(key);
// arrays get copied by value when copying structures
// see the CFMX docs on the StructCopy function
if (val instanceof cfArrayData)
copy.put(key, ((cfArrayData) val).duplicate());
}
return copy;
}
public synchronized cfData duplicate() {
Map<String, cfData> dupData = cloneHashdata();
Object[] keys = keys();
for (int i = 0; i < keys.length; i++) {
cfData nextDataCopy = null;
String nextKey = (String) keys[i];
cfData nextData = getData(nextKey);
if (nextData != null) {
if (nextData.isImplicit()) // part of the fix for bug #2083
continue;
nextDataCopy = nextData.duplicate();
if (nextDataCopy == null) { // return null if struct contains
// non-duplicatable type
return null;
}
}
dupData.put(nextKey, nextDataCopy);
}
return new cfStructData(dupData);
}
public String getKeyList(String delimiter) {
String list = "";
Object[] keys = keys();
for (int i = 0; i < keys.length; i++) {
list += (String) keys[i];
list += delimiter;
}
if (size() > 0)
list = list.substring(0, list.length() - delimiter.length());
return list;
}
public synchronized String toString() {
if (isBDAdminStruct)
return "{STRUCT: [toString() disabled]}";
StringBuilder tmp = new StringBuilder(20);
Object[] keys = keys();
for (int i = 0; i < keys.length; i++) {
String key = (String) keys[i];
tmp.append(key + "=" + getData(key) + ",");
}
String tS = tmp.toString();
if (tS.length() > 0)
tS = tS.substring(0, tS.length() - 1);
return "{STRUCT:" + tS + "}";
}
public void dump(java.io.PrintWriter out) {
dump(out, false, "", cfDUMP.TOP_DEFAULT);
}
public void dump(java.io.PrintWriter out, String _lbl, int _top) {
dump(out, false, _lbl, _top);
}
public void dumpLong(java.io.PrintWriter out) {
dump(out, true, "", cfDUMP.TOP_DEFAULT);
}
public void dumpLong(java.io.PrintWriter out, String _lbl, int _top) {
dump(out, true, _lbl, _top);
}
protected synchronized void dump(java.io.PrintWriter out, boolean longVersion, String _lbl, int _top) {
dump("struct",out,longVersion,_lbl,_top);
}
protected synchronized void dump(String tablename, java.io.PrintWriter out, boolean longVersion, String _lbl, int _top) {
out.write("<table class='cfdump_table_struct'>");
out.write("<th class='cfdump_th_struct' colspan='2'>");
if (_lbl.length() > 0)
out.write(_lbl + " - ");
Object[] keys = keys();
if (isBDAdminStruct) {
out.write( tablename + " [dump disabled]</th>");
} else if (keys.length > 0) {
out.write( tablename + "</th>");
java.util.Arrays.sort(keys);
for (int i = 0; i < keys.length; i++) {
String key = (String) keys[i];
out.write("<tr><td class='cfdump_td_struct'>");
out.write(key);
out.write("</td><td class='cfdump_td_value'>");
if (_top > 1) {
cfData dd = getData(key);
if (dd != null) {
int newTop = (dd.getDataType() == cfData.CFSTRUCTDATA ? _top - 1 : _top);
if (longVersion)
dd.dumpLong(out, "", newTop);
else
dd.dump(out, "", newTop);
} else {
out.write("[null]");
}
}
out.write("</td></tr>");
}
} else {
out.write( tablename + " [empty]</th>");
}
out.write("</table>");
}
public void dumpWDDX(int version, java.io.PrintWriter out) {
if (version > 10)
out.write("<s>");
else
out.write("<struct>");
Object[] keys = keys();
String key;
for (int i = 0; i < keys.length; i++) {
key = (String) keys[i];
if (version > 10)
out.write("<v n='");
else
out.write("<var name='");
out.write(key);
out.write("'>");
getData(key).dumpWDDX(version, out);
if (version > 10)
out.write("</v>");
else
out.write("</var>");
}
if (version > 10)
out.write("</s>");
else
out.write("</struct>");
}
/*******************************************************************************
* The following methods implement the java.util.Map interface. They're
* implemented to support variable sharing between CFML and servlets/JSP
* pages. These methods automatically convert between internal BlueDragon data
* types and "natural" Java data types.
*
* Subclasses that use an alternate data store (intead of hashdata) and that
* store "natural" Java objects will be more efficient if they override the
* get, put, and containsValue methods to avoid converting from Java objects
* to CFML variables and back again. (However, this isn't necessary for
* correct operation).
*
* These methods MUST not reference the underlying hashdata directly!
*******************************************************************************/
// Returns true if this map contains a mapping for the specified key.
public boolean containsKey(Object key) {
return containsKey(key.toString());
}
// Returns true if this map maps one or more keys to the specified value.
public boolean containsValue(Object value) {
return containsValue(tagUtils.convertToCfData(value));
}
// Returns the value to which this map maps the specified key.
public Object get(Object key) {
return tagUtils.getNatural((getData(key.toString())));
}
// Associates the specified value with the specified key in this map.
public Object put(Object key, Object value) {
Object oldValue = get(key);
setData(key.toString(), tagUtils.convertToCfData(value));
return oldValue;
}
// Copies all of the mappings from the specified map to this map.
public void putAll(Map m) {
Iterator<? extends String> iter = m.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
Object val = m.get(key);
put(key, val);
}
}
// Removes the mapping for this key from this map if it is present.
public Object remove(Object key) {
try {
Object value = get(key);
deleteData(key.toString());
return value;
} catch (cfmRunTimeException exc) {
throw new IllegalArgumentException(exc.getMessage());
}
}
// convert values to natural Java objects
public synchronized Collection<Object> values() {
return tagUtils.getNaturalMap(this).values();
}
// convert values to natural Java objects
public synchronized Set<Entry<String, Object>> entrySet() {
return tagUtils.getNaturalMap(this).entrySet();
}
/**
* 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);
Object[] keys = keys();
for ( int x = 0; x < keys.length; x++ ){
args.clear();
args.add( new cfStringData( String.valueOf(keys[x]) ) );
args.add( getData( (String)keys[x] ) );
udf.execute( SessionData.Session, args );
}
}
}