gt: 			getterMap = new HashMap<String, Method>();
context: package org.red5.io.amf;
  
  /*
   * RED5 Open Source Flash Server - http://www.osflash.org/red5
   *
   * Copyright (c) 2006-2009 by respective authors (see below). All rights reserved.
   *
   * This library is free software; you can redistribute it and/or modify it under the
   * terms of the GNU Lesser General Public License as published by the Free Software
   * Foundation; either version 2.1 of the License, or (at your option) any later
   * version.
   *
   * This library 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 Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License along
   * with this library; if not, write to the Free Software Foundation, Inc.,
   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
   */
  
  import java.io.File;
  import java.lang.reflect.Array;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.nio.ByteBuffer;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;
  import java.util.TimeZone;
  
  import net.sf.ehcache.Cache;
  import net.sf.ehcache.CacheException;
  import net.sf.ehcache.CacheManager;
  import net.sf.ehcache.Element;
  
  import org.apache.commons.beanutils.BeanMap;
  import org.apache.mina.core.buffer.IoBuffer;
  import org.red5.annotations.Anonymous;
  import org.red5.io.amf3.ByteArray;
  import org.red5.io.object.BaseOutput;
  import org.red5.io.object.ICustomSerializable;
  import org.red5.io.object.RecordSet;
  import org.red5.io.object.Serializer;
  import org.red5.io.utils.XMLUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.w3c.dom.Document;
  
  /**
   *
   * @author The Red5 Project (red5@osflash.org)
   * @author Luke Hubbard, Codegent Ltd (luke@codegent.com)
   * @author Paul Gregoire (mondain@gmail.com)
   * @author Harald Radi (harald.radi@nme.at)
   */
  public class Output extends BaseOutput implements org.red5.io.object.Output {
  
  	protected static Logger log = LoggerFactory.getLogger(Output.class);
  
  	/**
  	 * Cache encoded strings... the TK way...
  	 */
  	private static Cache stringCache;
  
  	private static Cache serializeCache;
  
  	private static Cache fieldCache;
  
  	private static Cache getterCache;
  
  	private static CacheManager cacheManager;
  
  	private static CacheManager getCacheManager() {
  		if (cacheManager == null) {
  			if (System.getProperty("red5.root") != null) {
  				//    			we're running Red5 as a server.
  				try {
  					cacheManager = new CacheManager(System.getProperty("red5.root") + File.separator + "conf"
  							+ File.separator + "ehcache.xml");
  				} catch (CacheException e) {
  					cacheManager = constructDefault();
  				}
  			} else {
  				//    			not a server, maybe running tests? 
  				cacheManager = constructDefault();
  			}
  		}
  
  		return cacheManager;
  	}
  
  	private static CacheManager constructDefault() {
  		CacheManager manager = new CacheManager();
  		if (!manager.cacheExists("org.red5.io.amf.Output.stringCache"))
  			manager.addCache("org.red5.io.amf.Output.stringCache");
  		if (!manager.cacheExists("org.red5.io.amf.Output.getterCache"))
  			manager.addCache("org.red5.io.amf.Output.getterCache");
  		if (!manager.cacheExists("org.red5.io.amf.Output.fieldCache"))
  			manager.addCache("org.red5.io.amf.Output.fieldCache");
  		if (!manager.cacheExists("org.red5.io.amf.Output.serializeCache"))
  			manager.addCache("org.red5.io.amf.Output.serializeCache");
  		return manager;
  	}
  
  	/**
  	 * Output buffer
  	 */
  	protected IoBuffer buf;
  
  	/**
  	 * Creates output with given byte buffer
  	 * @param buf         Bute buffer
  	 */
  	public Output(IoBuffer buf) {
  		super();
  		this.buf = buf;
  	}
  
  	/** {@inheritDoc} */
  	public boolean isCustom(Object custom) {
  		return false;
  	}
  
  	protected boolean checkWriteReference(Object obj) {
  		if (hasReference(obj)) {
  			writeReference(obj);
  			return true;
  		} else
  			return false;
  	}
  
  	/** {@inheritDoc} */
  	public void writeArray(Collection<?> array, Serializer serializer) {
  		if (checkWriteReference(array)) {
  			return;
  		}
  		storeReference(array);
  		buf.put(AMF.TYPE_ARRAY);
  		buf.putInt(array.size());
  		for (Object item : array) {
  			serializer.serialize(this, item);
  		}
  	}
  
  	/** {@inheritDoc} */
  	public void writeArray(Object[] array, Serializer serializer) {
  		log.debug("writeArray - array: {} serializer: {}", array, serializer);
  		if (array != null) {
  			if (checkWriteReference(array)) {
  				return;
  			}
  			storeReference(array);
  			buf.put(AMF.TYPE_ARRAY);
  			buf.putInt(array.length);
  			for (Object item : array) {
  				serializer.serialize(this, item);
  			}
  		} else {
  			writeNull();
  		}
  	}
  
  	/** {@inheritDoc} */
  	public void writeArray(Object array, Serializer serializer) {
  		if (array != null) {
  			if (checkWriteReference(array)) {
  				return;
  			}
  			storeReference(array);
  			buf.put(AMF.TYPE_ARRAY);
  			buf.putInt(Array.getLength(array));
  			for (int i = 0; i < Array.getLength(array); i++) {
  				serializer.serialize(this, Array.get(array, i));
  			}
  		} else {
  			writeNull();
  		}
  	}
  
  	/** {@inheritDoc} */
  	public void writeMap(Map<Object, Object> map, Serializer serializer) {
  		if (checkWriteReference(map)) {
  			return;
  		}
  		storeReference(map);
  		buf.put(AMF.TYPE_MIXED_ARRAY);
  		int maxInt = -1;
  		for (int i = 0; i < map.size(); i++) {
  			try {
  				if (!map.containsKey(i))
  					break;
  			} catch (ClassCastException err) {
  				// Map has non-number keys.
  				break;
  			}
  
  			maxInt = i;
  		}
  		buf.putInt(maxInt + 1);
  		// TODO: Need to support an incoming key named length
  		for (Map.Entry<Object, Object> entry : map.entrySet()) {
  			final String key = entry.getKey().toString();
  			if ("length".equals(key)) {
  				continue;
  			}
  			putString(key);
  			serializer.serialize(this, entry.getValue());
  		}
  		if (maxInt >= 0) {
  			putString("length");
  			serializer.serialize(this, maxInt + 1);
  		}
  		buf.put((byte) 0x00);
  		buf.put((byte) 0x00);
  		buf.put(AMF.TYPE_END_OF_OBJECT);
  	}
  
  	/** {@inheritDoc} */
  	public void writeMap(Collection<?> array, Serializer serializer) {
  		if (checkWriteReference(array)) {
  			return;
  		}
  		storeReference(array);
  		buf.put(AMF.TYPE_MIXED_ARRAY);
  		buf.putInt(array.size() + 1);
  		int idx = 0;
  		for (Object item : array) {
  			if (item != null) {
  				putString(String.valueOf(idx++));
  				serializer.serialize(this, item);
  			} else {
  				idx++;
  			}
  		}
  		putString("length");
  		serializer.serialize(this, array.size() + 1);
  
  		buf.put((byte) 0x00);
  		buf.put((byte) 0x00);
  		buf.put(AMF.TYPE_END_OF_OBJECT);
  	}
  
  	/** {@inheritDoc} */
  	public void writeRecordSet(RecordSet recordset, Serializer serializer) {
  		if (checkWriteReference(recordset)) {
  			return;
  		}
  		storeReference(recordset);
  		// Write out start of object marker
  		buf.put(AMF.TYPE_CLASS_OBJECT);
  		putString("RecordSet");
  		// Serialize
  		Map<String, Object> info = recordset.serialize();
  		// Write out serverInfo key
  		putString("serverInfo");
  		// Serialize
  		serializer.serialize(this, info);
  		// Write out end of object marker
  		buf.put((byte) 0x00);
  		buf.put((byte) 0x00);
  		buf.put(AMF.TYPE_END_OF_OBJECT);
  	}
  
  	/** {@inheritDoc} */
  	public boolean supportsDataType(byte type) {
  		return false;
  	}
  
  	/** {@inheritDoc} */
  	public void writeBoolean(Boolean bol) {
  		buf.put(AMF.TYPE_BOOLEAN);
  		buf.put(bol ? AMF.VALUE_TRUE : AMF.VALUE_FALSE);
  	}
  
  	/** {@inheritDoc} */
  	public void writeCustom(Object custom) {
  
  	}
  
  	/** {@inheritDoc} */
  	public void writeDate(Date date) {
  		buf.put(AMF.TYPE_DATE);
  		buf.putDouble(date.getTime());
  		buf.putShort((short) (TimeZone.getDefault().getRawOffset() / 60 / 1000));
  	}
  
  	/** {@inheritDoc} */
  	public void writeNull() {
  		// System.err.println("Write null");
  		buf.put(AMF.TYPE_NULL);
  	}
  
  	/** {@inheritDoc} */
  	public void writeNumber(Number num) {
  		buf.put(AMF.TYPE_NUMBER);
  		buf.putDouble(num.doubleValue());
  	}
  
  	/** {@inheritDoc} */
  	public void writeReference(Object obj) {
  		log.debug("Write reference");
  		buf.put(AMF.TYPE_REFERENCE);
  		buf.putShort(getReferenceId(obj));
  	}
  
  	/** {@inheritDoc} */
  	@SuppressWarnings({ "rawtypes", "unchecked" })
  	public void writeObject(Object object, Serializer serializer) {
  		if (checkWriteReference(object)) {
  			return;
  		}
  		storeReference(object);
  		// Create new map out of bean properties
  		BeanMap beanMap = new BeanMap(object);
  		// Set of bean attributes
  		Set set = beanMap.keySet();
  		if ((set.size() == 0) || (set.size() == 1 && beanMap.containsKey("class"))) {
  			// BeanMap is empty or can only access "class" attribute, skip it
  			writeArbitraryObject(object, serializer);
  			return;
  		}
  
  		// Write out either start of object marker for class name or "empty" start of object marker
  		Class<?> objectClass = object.getClass();
  		if (!objectClass.isAnnotationPresent(Anonymous.class)) {
  			buf.put(AMF.TYPE_CLASS_OBJECT);
  			putString(buf, serializer.getClassName(objectClass));
  		} else {
  			buf.put(AMF.TYPE_OBJECT);
  		}
  
  		if (object instanceof ICustomSerializable) {
  			((ICustomSerializable) object).serialize(this, serializer);
  			buf.put((byte) 0x00);
  			buf.put((byte) 0x00);
  			buf.put(AMF.TYPE_END_OF_OBJECT);
  			return;
  		}
  
  		// Iterate thru entries and write out property names with separators
  		for (Object key : set) {
  			String fieldName = key.toString();
  			log.debug("Field name: {} class: {}", fieldName, objectClass);
  
  			Field field = getField(objectClass, fieldName);
  			Method getter = getGetter(objectClass, beanMap, fieldName);
  
  			// Check if the Field corresponding to the getter/setter pair is transient
  			if (!serializeField(serializer, objectClass, fieldName, field, getter)) {
  				continue;
  			}
  
  			putString(buf, fieldName);
  			serializer.serialize(this, field, getter, object, beanMap.get(key));
  		}
  		// Write out end of object mark
  		buf.put((byte) 0x00);
  		buf.put((byte) 0x00);
  		buf.put(AMF.TYPE_END_OF_OBJECT);
  	}
  
  	@SuppressWarnings("unchecked")
  	protected boolean serializeField(Serializer serializer, Class<?> objectClass, String keyName, Field field,
  			Method getter) {
  		//		to prevent, NullPointerExceptions, get the element first and check if it's null. 
  		Element element = getSerializeCache().get(objectClass);
  		Map<String, Boolean> serializeMap = (element == null ? null : (Map<String, Boolean>) element.getObjectValue());
  
  		if (serializeMap == null) {
  			serializeMap = new HashMap<String, Boolean>();
  			getSerializeCache().put(new Element(objectClass, serializeMap));
  		}
  
  		Boolean serialize;
  		if (getSerializeCache().isKeyInCache(keyName)) {
  			serialize = serializeMap.get(keyName);
  		} else {
  			serialize = serializer.serializeField(keyName, field, getter);
  			serializeMap.put(keyName, serialize);
  		}
  
  		return serialize;
  	}
  
  	@SuppressWarnings("unchecked")
  	protected Field getField(Class<?> objectClass, String keyName) {
  		//again, to prevent null pointers, check if the element exists first.
  		Element element = getFieldCache().get(objectClass);
  		Map<String, Field> fieldMap = (element == null ? null : (Map<String, Field>) element.getObjectValue());
  		if (fieldMap == null) {
  			fieldMap = new HashMap<String, Field>();
  			getFieldCache().put(new Element(objectClass, fieldMap));
  		}
  
  		Field field = null;
  
  		if (fieldMap.containsKey(keyName)) {
  			field = fieldMap.get(keyName);
  		} else {
  			for (Class<?> clazz = objectClass; !clazz.equals(Object.class); clazz = clazz.getSuperclass()) {
  				Field[] fields = clazz.getDeclaredFields();
  				if (fields.length > 0) {
  					for (Field fld : fields) {
  						if (fld.getName().equals(keyName)) {
  							field = fld;
  							break;
  						}
  					}
  				}
  			}
  
  			fieldMap.put(keyName, field);
  		}
  
  		return field;
  	}
  
  	@SuppressWarnings("unchecked")
  	protected Method getGetter(Class<?> objectClass, BeanMap beanMap, String keyName) {
  		//check element to prevent null pointer
  		Element element = getGetterCache().get(objectClass);
  		Map<String, Method> getterMap = (element == null ? null : (Map<String, Method>) element.getObjectValue());
  		if (getterMap == null) {
  