001: /******************************************************************************
002: * LZReturnObjectSWF.java
003: * ****************************************************************************/package org.openlaszlo.remote;
004:
005: import java.io.*;
006: import java.lang.reflect.*;
007: import java.util.*;
008: import java.lang.reflect.*;
009: import javax.servlet.http.*;
010: import org.openlaszlo.iv.flash.util.*;
011: import org.openlaszlo.iv.flash.api.action.*;
012: import org.openlaszlo.iv.flash.api.*;
013: import org.openlaszlo.utils.*;
014: import org.openlaszlo.xml.internal.*;
015: import org.apache.log4j.*;
016: import org.apache.commons.beanutils.PropertyUtils;
017:
018: /**
019: * Utility class to create object SWF based on a server object.
020: */
021: public class LZReturnObjectSWF {
022: public static Logger mLogger = Logger
023: .getLogger(LZReturnObjectSWF.class);
024: public static final int RETTYPE_POJO = 0;
025: public static final int RETTYPE_JAVA_BEAN = 1;
026:
027: int mCount = 0;
028: int mSize = 4096;
029: FlashBuffer mBody = new FlashBuffer(mSize);
030: Program mProgram = new Program(mBody);
031: DataContext mDC;
032: int mSWFVersion;
033: int mObjRetType;
034:
035: public LZReturnObjectSWF(String objectReturnType, int swfversion) {
036: mDC = new DataContext();
037: mDC.setEncoding(swfversion == 5 ? "Cp1252" : "UTF-8");
038: if (objectReturnType == null) {
039: mObjRetType = RETTYPE_POJO;
040: } else if ("javabean".equals(objectReturnType)) {
041: mObjRetType = RETTYPE_JAVA_BEAN;
042: } else {
043: mObjRetType = RETTYPE_POJO;
044: }
045: }
046:
047: void pushInteger(int i) {
048: mLogger.debug("pushInteger");
049: mProgram.push(i);
050: }
051:
052: void pushFloat(float f) {
053: mLogger.debug("pushFloat");
054: mProgram.push(f);
055: }
056:
057: void pushString(String s) {
058: mLogger.debug("pushString");
059: DataCommon.pushStringData(s, mBody, mDC);
060: // mProgram.push(s);
061: }
062:
063: void pushDouble(double d) {
064: mLogger.debug("pushDouble");
065: mBody.writeByte(Actions.PushData);
066: mBody.writeWord(8 + 1);
067: mBody.writeByte(6);
068: long dbits = Double.doubleToLongBits(d);
069: mBody.writeDWord((int) (dbits >>> 32));
070: mBody.writeDWord((int) (dbits & 0xffffffffL));
071: }
072:
073: void pushBoolean(boolean b) {
074: mLogger.debug("pushBoolean");
075: mBody.writeByte(Actions.PushData);
076: mBody.writeWord(1 + 1);
077: mBody.writeByte(5);
078: mBody.writeByte(b ? 1 : 0);
079: }
080:
081: void pushArray(Object object) {
082: mLogger.debug("pushArray");
083: int length = Array.getLength(object);
084: for (int i = length - 1; 0 <= i; i--) {
085: createReturnValue(Array.get(object, i));
086: }
087: mProgram.push(length);
088: mBody.writeByte(Actions.InitArray);
089: }
090:
091: void pushList(Object object) {
092: mLogger.debug("pushList");
093: List list = (List) object;
094: int length = list.size();
095: for (int i = length - 1; 0 <= i; i--) {
096: createReturnValue(list.get(i));
097: }
098: mProgram.push(length);
099: mBody.writeByte(Actions.InitArray);
100: }
101:
102: void pushNull() {
103: mBody.writeByte(Actions.PushData);
104: mBody.writeWord(0 + 1);
105: mBody.writeByte(2);
106: }
107:
108: void pushObject(Object object) {
109: Class cl = object.getClass();
110:
111: // Does this have to be a unique name?
112: String varname = "__tmp" + (mCount++);
113: String classname = cl.getName();
114:
115: //------------------------------------------------------------
116: // varname = new Object();
117: mProgram.push(varname);
118: {
119: // new Object()
120: mProgram.push(0); // zero arguments
121: mProgram.push("Object"); // push classname
122: mProgram.newObject(); // instantiate
123: }
124: mProgram.setVar();
125:
126: //------------------------------------------------------------
127: // varname.class = classname
128: mProgram.push(varname);
129: mProgram.getVar();
130: mProgram.push("class");
131: mProgram.push(classname);
132: mBody.writeByte(Actions.SetMember);
133:
134: if (mObjRetType == RETTYPE_JAVA_BEAN) {
135: pushObjectJavaBean(object, varname);
136: } else {
137: pushObjectPOJO(object, varname);
138: }
139:
140: //------------------------------------------------------------
141: // add varname object into stack
142: mProgram.push(varname);
143: mProgram.getVar();
144: }
145:
146: /**
147: * Create SWF for an object that conforms to JavaBean spec.
148: */
149: void pushObjectPOJO(Object object, String varname) {
150: Class cl = object.getClass();
151: Field[] fields = cl.getFields();
152: for (int i = 0; i < fields.length; i++) {
153: if (!Modifier.isPublic(fields[i].getModifiers()))
154: continue;
155:
156: String fieldName = fields[i].getName();
157: Object value;
158: try {
159: value = fields[i].get(object);
160: } catch (IllegalAccessException e) {
161: mLogger.error("IllegalAccessException", e);
162: continue;
163: }
164: if (mLogger.isDebugEnabled()) {
165: mLogger.debug("add field name " + fieldName + ", "
166: + (value != null ? value.getClass() : null));
167: }
168: mProgram.push(varname);
169: mProgram.getVar();
170: mProgram.push(fieldName);
171: createReturnValue(value);
172: mBody.writeByte(Actions.SetMember);
173: }
174: }
175:
176: /**
177: * Create SWF for an object that conforms to JavaBean spec.
178: */
179: void pushObjectJavaBean(Object object, String varname) {
180: //------------------------------------------------------------
181: // Just get the fields from the objects and add it to this object
182: Map beanProps = null;
183: try {
184: //Use jakarta-commons beanutils to inspect the object
185: beanProps = PropertyUtils.describe(object);
186: } catch (IllegalAccessException e) {
187: mLogger.error("IllegalAccessException", e);
188: } catch (InvocationTargetException e) {
189: mLogger.error("InvocationTargetException", e);
190: } catch (NoSuchMethodException e) {
191: mLogger.error("NoSuchMethodException", e);
192: }
193:
194: if (beanProps != null) {
195: Set keys = beanProps.keySet();
196: Iterator iter = keys.iterator();
197: while (iter.hasNext()) {
198: String fieldName = (String) iter.next();
199: //Don't add the class property as it is already set by the method
200: if (!"class".equals(fieldName)) {
201: Object value = beanProps.get(fieldName);
202: if (mLogger.isDebugEnabled()) {
203: mLogger.debug("add field name "
204: + fieldName
205: + ", "
206: + ((value != null) ? value.getClass()
207: : null));
208: }
209: mProgram.push(varname);
210: mProgram.getVar();
211: mProgram.push((String) fieldName);
212: createReturnValue(value);
213: mBody.writeByte(Actions.SetMember);
214: }
215: }
216: }
217: }
218:
219: void pushMap(Map map) {
220:
221: // Does this have to be a unique name?
222: String varname = "__tmp" + (mCount++);
223:
224: //------------------------------------------------------------
225: // varname = new Object();
226: mProgram.push(varname);
227: {
228: // new Object()
229: mProgram.push(0); // zero arguments
230: mProgram.push("Object"); // push classname
231: mProgram.newObject(); // instantiate
232: }
233: mProgram.setVar();
234:
235: Iterator iter = map.keySet().iterator();
236:
237: while (iter.hasNext()) {
238: String key = (String) iter.next();
239:
240: //------------------------------------------------------------
241: // varname.class = classname
242: mProgram.push(varname);
243: mProgram.getVar();
244: mProgram.push(key);
245: createReturnValue(map.get(key));
246: mBody.writeByte(Actions.SetMember);
247: }
248:
249: //------------------------------------------------------------
250: // add varname object into stack
251: mProgram.push(varname);
252: mProgram.getVar();
253: }
254:
255: /**
256: * Recurse through this function to create return value
257: */
258: void createReturnValue(Object object) {
259: mLogger.debug("createReturnValue");
260: if (object == null) {
261: pushNull();
262: return;
263: }
264:
265: Class cl = object.getClass();
266: if (cl.isArray()) {
267: pushArray(object);
268: } else if (List.class.isInstance(object)) {
269: pushList(object);
270: } else if (Map.class.isInstance(object)) {
271: pushMap((Map) object);
272: } else if (cl == Integer.class) {
273: pushInteger(((Integer) object).intValue());
274: } else if (cl == Long.class) {
275: //------------------------------------------------------------
276: // From: http://developer.irt.org/script/1031.htm
277: //
278: // In JavaScript all numbers are floating-point numbers.
279: //
280: // JavaScript uses the standard 8 byte IEEE floating-point numeric
281: // format, which means the range is from:
282: //
283: // +/- 1.7976931348623157x10^308 - very large, and +/- 5x10^-324 -
284: // very small.
285: //
286: // As JavaScript uses floating-point numbers the accuracy is only
287: // assured for integers between: -9,007,199,254,740,992 (-2^53) and
288: // 9,007,199,254,740,992 (2^53)
289: //
290: // All the above from "JavaScript The Definitive Guide" - O'Reilly.
291: //
292: //------------------------------------------------------------
293: // Java long:
294: // 8 bytes signed (two's complement). Ranges from
295: // -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807.
296: //------------------------------------------------------------
297:
298: // possible rounding inaccuracy
299: pushInteger(((Long) object).intValue());
300:
301: } else if (cl == Short.class) {
302: pushInteger(((Short) object).intValue());
303: } else if (cl == Byte.class) {
304: // push as number for now
305: pushInteger(((Byte) object).intValue());
306: } else if (cl == Character.class) {
307: pushString(((Character) object).toString());
308: } else if (cl == Float.class) {
309: pushFloat(((Float) object).floatValue());
310: } else if (cl == Double.class) {
311: pushDouble(((Double) object).doubleValue());
312: } else if (cl == Boolean.class) {
313: pushBoolean(((Boolean) object).booleanValue());
314: } else if (cl == String.class) {
315: pushString((String) object);
316: } else {
317: pushObject(object);
318: }
319: }
320:
321: /**
322: *
323: */
324: public Program createObjectProgram(Object object) {
325: mLogger.debug("createObjectProgram(" + object + ")");
326:
327: createReturnValue(object);
328:
329: //------------------------------------------------------------
330: // call into the viewsystem
331: mProgram.push("_parent");
332: mProgram.getVar();
333: mProgram.push(2);
334: mProgram.push("_parent");
335: mProgram.getVar();
336: mProgram.push("loader");
337: mBody.writeByte(Actions.GetMember);
338: mProgram.push("returnData");
339: mProgram.callMethod();
340: mProgram.pop();
341:
342: // Borrowed from DataCompiler. -pk
343:
344: // Collect the string dictionary data
345: byte pooldata[] = DataCommon.makeStringPool(mDC);
346:
347: // Room at the end of the buffer for maybe some callback code to the
348: // runtime to say we're done.
349: final int MISC = 4096;
350:
351: // 'out' is the main FlashBuffer for composing the output file
352: FlashBuffer out = new FlashBuffer(mBody.getSize()
353: + pooldata.length + MISC);
354: // Write out string constant pool
355: out._writeByte(Actions.ConstantPool);
356: out._writeWord(pooldata.length + 2); // number of bytes in pool data + int (# strings)
357: out._writeWord(mDC.cpool.size()); // number of strings in pool
358: out.writeArray(pooldata, 0, pooldata.length); // copy the data
359:
360: // Write out the code to build nodes
361: out.writeArray(mBody.getBuf(), 0, mBody.getSize());
362: return new Program(out);
363: }
364:
365: /**
366: */
367: public static FlashFile createObjectFile(Object object,
368: String objectReturnType, int swfversion) throws IOException {
369: mLogger.debug("createObjectFile(" + object + ", " + swfversion
370: + ")");
371: // Create FlashFile object nd include action bytes
372: FlashFile file = FlashFile.newFlashFile();
373: Script s = new Script(1);
374: file.setMainScript(s);
375: file.setVersion(swfversion);
376: Frame frame = s.newFrame();
377: Program program = new LZReturnObjectSWF(objectReturnType,
378: swfversion).createObjectProgram(object);
379: frame.addFlashObject(new DoAction(program));
380: return file;
381: }
382:
383: /**
384: * @param objectReturnType One of 'pojo' (returns public member values) or
385: * 'javabean' (returns members that have associated getters). Will default
386: * to 'pojo'.
387: */
388: public static byte[] createObject(Object object,
389: String objectReturnType, int swfversion) throws IOException {
390: mLogger.debug("createObject(" + object + ")");
391:
392: int i = 0;
393: try {
394: FlashFile file = createObjectFile(object, objectReturnType,
395: swfversion);
396: FlashOutput fob = file.generate();
397: byte[] buf = new byte[fob.getSize()];
398: System.arraycopy(fob.getBuf(), 0, buf, 0, fob.getSize());
399: return buf;
400: } catch (IVException e) {
401: throw new ChainedException(e);
402: } catch (IOException e) {
403: mLogger.error("io error creating object SWF: "
404: + e.getMessage());
405: throw e;
406: }
407: }
408:
409: }
|