001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.util.io;
018:
019: import java.io.DataOutputStream;
020: import java.io.IOException;
021: import java.io.ObjectOutput;
022: import java.io.ObjectOutputStream;
023: import java.io.OutputStream;
024: import java.io.Serializable;
025: import java.lang.reflect.Array;
026: import java.util.Arrays;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030: import java.util.Map.Entry;
031:
032: /**
033: * @author jcompagner
034: */
035: public final class WicketObjectOutputStream extends ObjectOutputStream {
036: /**
037: * Lightweight identity hash table which maps objects to integer handles,
038: * assigned in ascending order (comes from {@link ObjectOutputStream}).
039: */
040: private static final class HandleTable {
041: /* number of mappings in table/next available handle */
042: private int size;
043: /* size threshold determining when to expand hash spine */
044: private int threshold;
045: /* factor for computing size threshold */
046: private final float loadFactor;
047: /* maps hash value -> candidate handle value */
048: private int[] spine;
049: /* maps handle value -> next candidate handle value */
050: private int[] next;
051: /* maps handle value -> associated object */
052: private Object[] objs;
053:
054: HandleTable() {
055: this (16, 0.75f);
056: }
057:
058: HandleTable(int initialCapacity, float loadFactor) {
059: this .loadFactor = loadFactor;
060: spine = new int[initialCapacity];
061: next = new int[initialCapacity];
062: objs = new Object[initialCapacity];
063: threshold = (int) (initialCapacity * loadFactor);
064: clear();
065: }
066:
067: private void growEntries() {
068: int newLength = (next.length << 1) + 1;
069: int[] newNext = new int[newLength];
070: System.arraycopy(next, 0, newNext, 0, size);
071: next = newNext;
072:
073: Object[] newObjs = new Object[newLength];
074: System.arraycopy(objs, 0, newObjs, 0, size);
075: objs = newObjs;
076: }
077:
078: private void growSpine() {
079: spine = new int[(spine.length << 1) + 1];
080: threshold = (int) (spine.length * loadFactor);
081: Arrays.fill(spine, -1);
082: for (int i = 0; i < size; i++) {
083: insert(objs[i], i);
084: }
085: }
086:
087: private int hash(Object obj) {
088: return System.identityHashCode(obj) & 0x7FFFFFFF;
089: }
090:
091: private void insert(Object obj, int handle) {
092: int index = hash(obj) % spine.length;
093: objs[handle] = obj;
094: next[handle] = spine[index];
095: spine[index] = handle;
096: }
097:
098: /**
099: * Assigns next available handle to given object, and returns handle
100: * value. Handles are assigned in ascending order starting at 0.
101: *
102: * @param obj
103: * @return
104: */
105: int assign(Object obj) {
106: if (size >= next.length) {
107: growEntries();
108: }
109: if (size >= threshold) {
110: growSpine();
111: }
112: insert(obj, size);
113: return size++;
114: }
115:
116: void clear() {
117: Arrays.fill(spine, -1);
118: Arrays.fill(objs, 0, size, null);
119: size = 0;
120: }
121:
122: boolean contains(Object obj) {
123: return lookup(obj) != -1;
124: }
125:
126: /**
127: * Looks up and returns handle associated with given object, or -1 if no
128: * mapping found.
129: *
130: * @param obj
131: * @return
132: */
133: int lookup(Object obj) {
134: if (size == 0) {
135: return -1;
136: }
137: int index = hash(obj) % spine.length;
138: for (int i = spine[index]; i >= 0; i = next[i]) {
139: if (objs[i] == obj) {
140: return i;
141: }
142: }
143: return -1;
144: }
145:
146: int size() {
147: return size;
148: }
149: }
150:
151: private class PutFieldImpl extends PutField {
152: private HashMap mapBytes;
153: private HashMap mapChar;
154: private HashMap mapDouble;
155: private HashMap mapFloat;
156: private HashMap mapInt;
157: private HashMap mapLong;
158: private HashMap mapShort;
159: private HashMap mapBoolean;
160: private HashMap mapObject;
161:
162: /**
163: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String,
164: * boolean)
165: */
166: public void put(String name, boolean val) {
167: if (mapBoolean == null) {
168: mapBoolean = new HashMap(4);
169: }
170: mapBoolean.put(name, val ? Boolean.TRUE : Boolean.FALSE);
171: }
172:
173: /**
174: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String, byte)
175: */
176: public void put(String name, byte val) {
177: if (mapBytes == null) {
178: mapBytes = new HashMap(4);
179: }
180: mapBytes.put(name, new Byte(val));
181: }
182:
183: /**
184: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String, char)
185: */
186: public void put(String name, char val) {
187: if (mapChar == null) {
188: mapChar = new HashMap(4);
189: }
190: mapChar.put(name, new Character(val));
191: }
192:
193: /**
194: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String,
195: * double)
196: */
197: public void put(String name, double val) {
198: if (mapDouble == null) {
199: mapDouble = new HashMap(4);
200: }
201: mapDouble.put(name, new Double(val));
202: }
203:
204: /**
205: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String, float)
206: */
207: public void put(String name, float val) {
208: if (mapFloat == null) {
209: mapFloat = new HashMap(4);
210: }
211: mapFloat.put(name, new Float(val));
212: }
213:
214: /**
215: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String, int)
216: */
217: public void put(String name, int val) {
218: if (mapInt == null) {
219: mapInt = new HashMap(4);
220: }
221: mapInt.put(name, new Integer(val));
222: }
223:
224: /**
225: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String, long)
226: */
227: public void put(String name, long val) {
228: if (mapLong == null) {
229: mapLong = new HashMap(4);
230: }
231: mapLong.put(name, new Long(val));
232: }
233:
234: /**
235: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String,
236: * java.lang.Object)
237: */
238: public void put(String name, Object val) {
239: if (mapObject == null) {
240: mapObject = new HashMap(4);
241: }
242: mapObject.put(name, val);
243: }
244:
245: /**
246: * @see java.io.ObjectOutputStream.PutField#put(java.lang.String, short)
247: */
248: public void put(String name, short val) {
249: if (mapShort == null) {
250: mapShort = new HashMap(4);
251: }
252: mapShort.put(name, new Short(val));
253: }
254:
255: /**
256: * @see java.io.ObjectOutputStream.PutField#write(java.io.ObjectOutput)
257: */
258: public void write(ObjectOutput out) throws IOException {
259: // i don't know if all the fields (names in the map)
260: // are really also always real fields.. So i just
261: // write them by name->value
262: // maybe in the further we can really calculate an offset?
263: if (mapBoolean != null) {
264: ClassStreamHandler lookup = ClassStreamHandler
265: .lookup(boolean.class);
266: writeShort(lookup.getClassId());
267: writeShort(mapBoolean.size());
268: Iterator it = mapBoolean.entrySet().iterator();
269: while (it.hasNext()) {
270: Map.Entry entry = (Entry) it.next();
271: // write the key.
272: writeObjectOverride(entry.getKey());
273: writeBoolean(((Boolean) entry.getValue())
274: .booleanValue());
275: }
276: }
277: if (mapBytes != null) {
278: ClassStreamHandler lookup = ClassStreamHandler
279: .lookup(byte.class);
280: writeShort(lookup.getClassId());
281: writeShort(mapBytes.size());
282: Iterator it = mapBytes.entrySet().iterator();
283: while (it.hasNext()) {
284: Map.Entry entry = (Entry) it.next();
285: // write the key.
286: writeObjectOverride(entry.getKey());
287: writeByte(((Byte) entry.getValue()).byteValue());
288: }
289: }
290: if (mapShort != null) {
291: ClassStreamHandler lookup = ClassStreamHandler
292: .lookup(short.class);
293: writeShort(lookup.getClassId());
294: writeShort(mapShort.size());
295: Iterator it = mapShort.entrySet().iterator();
296: while (it.hasNext()) {
297: Map.Entry entry = (Entry) it.next();
298: // write the key.
299: writeObjectOverride(entry.getKey());
300: writeShort(((Short) entry.getValue()).shortValue());
301: }
302: }
303: if (mapChar != null) {
304: ClassStreamHandler lookup = ClassStreamHandler
305: .lookup(char.class);
306: writeShort(lookup.getClassId());
307: writeShort(mapChar.size());
308: Iterator it = mapChar.entrySet().iterator();
309: while (it.hasNext()) {
310: Map.Entry entry = (Entry) it.next();
311: // write the key.
312: writeObjectOverride(entry.getKey());
313: writeChar(((Character) entry.getValue())
314: .charValue());
315: }
316: }
317: if (mapInt != null) {
318: ClassStreamHandler lookup = ClassStreamHandler
319: .lookup(int.class);
320: writeShort(lookup.getClassId());
321: writeShort(mapInt.size());
322: Iterator it = mapInt.entrySet().iterator();
323: while (it.hasNext()) {
324: Map.Entry entry = (Entry) it.next();
325: // write the key.
326: writeObjectOverride(entry.getKey());
327: writeInt(((Integer) entry.getValue()).intValue());
328: }
329: }
330: if (mapLong != null) {
331: ClassStreamHandler lookup = ClassStreamHandler
332: .lookup(long.class);
333: writeShort(lookup.getClassId());
334: writeShort(mapLong.size());
335: Iterator it = mapLong.entrySet().iterator();
336: while (it.hasNext()) {
337: Map.Entry entry = (Entry) it.next();
338: // write the key.
339: writeObjectOverride(entry.getKey());
340: writeLong(((Long) entry.getValue()).longValue());
341: }
342: }
343: if (mapFloat != null) {
344: ClassStreamHandler lookup = ClassStreamHandler
345: .lookup(float.class);
346: writeShort(lookup.getClassId());
347: writeShort(mapFloat.size());
348: Iterator it = mapFloat.entrySet().iterator();
349: while (it.hasNext()) {
350: Map.Entry entry = (Entry) it.next();
351: // write the key.
352: writeObjectOverride(entry.getKey());
353: writeFloat(((Float) entry.getValue()).floatValue());
354: }
355: }
356: if (mapDouble != null) {
357: ClassStreamHandler lookup = ClassStreamHandler
358: .lookup(double.class);
359: writeShort(lookup.getClassId());
360: writeShort(mapDouble.size());
361: Iterator it = mapDouble.entrySet().iterator();
362: while (it.hasNext()) {
363: Map.Entry entry = (Entry) it.next();
364: // write the key.
365: writeObjectOverride(entry.getKey());
366: writeDouble(((Double) entry.getValue())
367: .doubleValue());
368: }
369: }
370: if (mapObject != null) {
371: ClassStreamHandler lookup = ClassStreamHandler
372: .lookup(Serializable.class);
373: writeShort(lookup.getClassId());
374: writeShort(mapObject.size());
375: Iterator it = mapObject.entrySet().iterator();
376: while (it.hasNext()) {
377: Map.Entry entry = (Entry) it.next();
378: // write the key.
379: writeObjectOverride(entry.getKey());
380: writeObjectOverride(entry.getValue());
381: }
382: }
383: // end byte.
384: writeShort(ClassStreamHandler.NULL);
385: }
386:
387: }
388:
389: private final HandleTable handledObjects = new HandleTable();
390:
391: private final HandleArrayListStack defaultWrite = new HandleArrayListStack();
392: private final DataOutputStream out;
393:
394: private ClassStreamHandler classHandler;
395:
396: private PutField curPut;
397:
398: private Object curObject;
399:
400: /**
401: * Construct.
402: *
403: * @param out
404: * @throws IOException
405: */
406: public WicketObjectOutputStream(OutputStream out)
407: throws IOException {
408: super ();
409: this .out = new DataOutputStream(out);
410:
411: }
412:
413: /**
414: * @see java.io.ObjectOutputStream#close()
415: */
416: public void close() throws IOException {
417: classHandler = null;
418: curObject = null;
419: curPut = null;
420: handledObjects.clear();
421: defaultWrite.clear();
422: out.close();
423: }
424:
425: /**
426: * @see java.io.ObjectOutputStream#defaultWriteObject()
427: */
428: public void defaultWriteObject() throws IOException {
429: if (!defaultWrite.contains(curObject)) {
430: defaultWrite.add(curObject);
431: classHandler.writeFields(this , curObject);
432: }
433: }
434:
435: /**
436: * @see java.io.ObjectOutputStream#putFields()
437: */
438: public PutField putFields() throws IOException {
439: if (curPut == null) {
440: try {
441: curPut = new PutFieldImpl();
442: } catch (Exception e) {
443: throw new WicketSerializeableException(
444: "Error reading put fields", e);
445: }
446: }
447: return curPut;
448: }
449:
450: /**
451: * @see java.io.ObjectOutputStream#write(byte[])
452: */
453: public void write(byte[] buf) throws IOException {
454: out.write(buf);
455: }
456:
457: /**
458: * @see java.io.ObjectOutputStream#write(byte[], int, int)
459: */
460: public void write(byte[] buf, int off, int len) throws IOException {
461: out.write(buf, off, len);
462: }
463:
464: /**
465: * @see java.io.ObjectOutputStream#write(int)
466: */
467: public void write(int val) throws IOException {
468: out.write(val);
469: }
470:
471: /**
472: * Writes a boolean.
473: *
474: * @param val
475: * the boolean to be written
476: * @throws IOException
477: * if I/O errors occur while writing to the underlying stream
478: */
479: public void writeBoolean(boolean val) throws IOException {
480: out.writeBoolean(val);
481: }
482:
483: /**
484: * Writes an 8 bit byte.
485: *
486: * @param val
487: * the byte value to be written
488: * @throws IOException
489: * if I/O errors occur while writing to the underlying stream
490: */
491: public void writeByte(int val) throws IOException {
492: out.writeByte(val);
493: }
494:
495: /**
496: * Writes a String as a sequence of bytes.
497: *
498: * @param str
499: * the String of bytes to be written
500: * @throws IOException
501: * if I/O errors occur while writing to the underlying stream
502: */
503: public void writeBytes(String str) throws IOException {
504: out.writeBytes(str);
505: }
506:
507: /**
508: * Writes a 16 bit char.
509: *
510: * @param val
511: * the char value to be written
512: * @throws IOException
513: * if I/O errors occur while writing to the underlying stream
514: */
515: public void writeChar(int val) throws IOException {
516: out.writeChar(val);
517: }
518:
519: /**
520: * Writes a String as a sequence of chars.
521: *
522: * @param str
523: * the String of chars to be written
524: * @throws IOException
525: * if I/O errors occur while writing to the underlying stream
526: */
527: public void writeChars(String str) throws IOException {
528: out.writeChars(str);
529: }
530:
531: /**
532: * Writes a 64 bit double.
533: *
534: * @param val
535: * the double value to be written
536: * @throws IOException
537: * if I/O errors occur while writing to the underlying stream
538: */
539: public void writeDouble(double val) throws IOException {
540: out.writeDouble(val);
541: }
542:
543: /**
544: * @see java.io.ObjectOutputStream#writeFields()
545: */
546: public void writeFields() throws IOException {
547: if (curPut != null) {
548: try {
549: curPut.write(this );
550: } catch (Exception e) {
551: throw new WicketSerializeableException(
552: "Error writing put fields", e);
553: }
554: }
555: }
556:
557: /**
558: * Writes a 32 bit float.
559: *
560: * @param val
561: * the float value to be written
562: * @throws IOException
563: * if I/O errors occur while writing to the underlying stream
564: */
565: public void writeFloat(float val) throws IOException {
566: out.writeFloat(val);
567: }
568:
569: /**
570: * Writes a 32 bit int.
571: *
572: * @param val
573: * the integer value to be written
574: * @throws IOException
575: * if I/O errors occur while writing to the underlying stream
576: */
577: public void writeInt(int val) throws IOException {
578: out.writeInt(val);
579: }
580:
581: /**
582: * Writes a 64 bit long.
583: *
584: * @param val
585: * the long value to be written
586: * @throws IOException
587: * if I/O errors occur while writing to the underlying stream
588: */
589: public void writeLong(long val) throws IOException {
590: out.writeLong(val);
591: }
592:
593: /**
594: * Writes a 16 bit short.
595: *
596: * @param val
597: * the short value to be written
598: * @throws IOException
599: * if I/O errors occur while writing to the underlying stream
600: */
601: public void writeShort(int val) throws IOException {
602: out.writeShort(val);
603: }
604:
605: /**
606: * @see java.io.ObjectOutputStream#writeUTF(java.lang.String)
607: */
608: public void writeUTF(String str) throws IOException {
609: out.writeUTF(str);
610: }
611:
612: /**
613: * @see java.io.ObjectOutputStream#writeObjectOverride(java.lang.Object)
614: */
615: protected final void writeObjectOverride(Object obj)
616: throws IOException {
617: if (obj == null) {
618: out.write(ClassStreamHandler.NULL);
619: return;
620: }
621: int handle = handledObjects.lookup(obj);
622: if (handle != -1) {
623: out.write(ClassStreamHandler.HANDLE);
624: out.writeShort(handle);
625: } else {
626: if (obj instanceof Class) {
627: ClassStreamHandler classHandler = ClassStreamHandler
628: .lookup((Class) obj);
629: out.write(ClassStreamHandler.CLASS);
630: out.writeShort(classHandler.getClassId());
631: } else {
632: Class cls = obj.getClass();
633: handledObjects.assign(obj);
634:
635: if (cls.isArray()) {
636: Class componentType = cls.getComponentType();
637: ClassStreamHandler classHandler = ClassStreamHandler
638: .lookup(componentType);
639: if (componentType.isPrimitive()) {
640: try {
641: out
642: .write(ClassStreamHandler.PRIMITIVE_ARRAY);
643: out.writeShort(classHandler.getClassId());
644: classHandler.writeArray(obj, this );
645: } catch (WicketSerializeableException wse) {
646: wse.addTrace(componentType.getName() + "["
647: + Array.getLength(obj) + "]");
648: throw wse;
649: } catch (Exception e) {
650: throw new WicketSerializeableException(
651: "Error writing primitive array of "
652: + componentType.getName()
653: + "["
654: + Array.getLength(obj)
655: + "]", e);
656: }
657: } else {
658: int length = Array.getLength(obj);
659: try {
660: out.write(ClassStreamHandler.ARRAY);
661: out.writeShort(classHandler.getClassId());
662: out.writeInt(length);
663: for (int i = 0; i < length; i++) {
664: writeObjectOverride(Array.get(obj, i));
665: }
666: } catch (WicketSerializeableException wse) {
667: wse.addTrace(componentType.getName() + "["
668: + length + "]");
669: throw wse;
670: } catch (Exception e) {
671: throw new WicketSerializeableException(
672: "Error writing array of "
673: + componentType.getName()
674: + "[" + length + "]", e);
675: }
676: }
677: return;
678: } else {
679: Class realClz = cls;
680: classHandler = ClassStreamHandler.lookup(realClz);
681:
682: Object object = classHandler.writeReplace(obj);
683: if (object != null) {
684: obj = object;
685: realClz = obj.getClass();
686: classHandler = ClassStreamHandler
687: .lookup(realClz);
688: }
689:
690: out.write(ClassStreamHandler.CLASS_DEF);
691: out.writeShort(classHandler.getClassId());
692: // handle strings directly.
693: if (obj instanceof String) {
694: out.writeUTF((String) obj);
695: } else {
696: PutField old = curPut;
697: Object oldObject = curObject;
698: curPut = null;
699: curObject = obj;
700: try {
701: if (!classHandler.invokeWriteMethod(this ,
702: obj)) {
703: classHandler.writeFields(this , obj);
704: }
705: } catch (WicketSerializeableException wse) {
706: if (realClz != cls) {
707: wse.addTrace(realClz.getName()
708: + "(ReplaceOf:" + cls.getName()
709: + ")");
710: } else {
711: wse.addTrace(realClz.getName());
712: }
713: throw wse;
714: } catch (Exception e) {
715: if (realClz != cls) {
716: throw new WicketSerializeableException(
717: "Error writing fields for "
718: + realClz.getName()
719: + "(ReplaceOf:"
720: + cls.getName() + ")",
721: e);
722:
723: } else {
724: throw new WicketSerializeableException(
725: "Error writing fields for "
726: + realClz.getName(), e);
727: }
728: } finally {
729: curObject = oldObject;
730: curPut = old;
731: }
732: }
733: }
734: }
735: }
736: }
737: }
|