KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jruby > runtime > marshal > MarshalStream


1 /***** BEGIN LICENSE BLOCK *****
2  * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Common Public
5  * License Version 1.0 (the "License"); you may not use this file
6  * except in compliance with the License. You may obtain a copy of
7  * the License at http://www.eclipse.org/legal/cpl-v10.html
8  *
9  * Software distributed under the License is distributed on an "AS
10  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11  * implied. See the License for the specific language governing
12  * rights and limitations under the License.
13  *
14  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
15  * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
16  * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
17  * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
18  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
19  * Copyright (C) 2006 Ola Bini <ola.bini@ki.se>
20  *
21  * Alternatively, the contents of this file may be used under the terms of
22  * either of the GNU General Public License Version 2 or later (the "GPL"),
23  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24  * in which case the provisions of the GPL or the LGPL are applicable instead
25  * of those above. If you wish to allow use of your version of this file only
26  * under the terms of either the GPL or the LGPL, and not to allow others to
27  * use your version of this file under the terms of the CPL, indicate your
28  * decision by deleting the provisions above and replace them with the notice
29  * and other provisions required by the GPL or the LGPL. If you do not delete
30  * the provisions above, a recipient may use your version of this file under
31  * the terms of any one of the CPL, the GPL or the LGPL.
32  ***** END LICENSE BLOCK *****/

33 package org.jruby.runtime.marshal;
34
35 import java.io.FilterOutputStream JavaDoc;
36 import java.io.IOException JavaDoc;
37 import java.io.OutputStream JavaDoc;
38 import java.util.Iterator JavaDoc;
39 import java.util.Map JavaDoc;
40 import org.jruby.Ruby;
41 import org.jruby.RubyArray;
42 import org.jruby.RubyBignum;
43 import org.jruby.RubyBoolean;
44 import org.jruby.RubyClass;
45 import org.jruby.RubyFixnum;
46 import org.jruby.RubyFloat;
47 import org.jruby.RubyHash;
48 import org.jruby.RubyModule;
49 import org.jruby.RubyRegexp;
50 import org.jruby.RubyString;
51 import org.jruby.RubyStruct;
52 import org.jruby.RubySymbol;
53 import org.jruby.runtime.ClassIndex;
54 import org.jruby.runtime.Constants;
55 import org.jruby.runtime.builtin.IRubyObject;
56 import org.jruby.util.ByteList;
57
58 /**
59  * Marshals objects into Ruby's binary marshal format.
60  *
61  * @author Anders
62  */

63 public class MarshalStream extends FilterOutputStream JavaDoc {
64     private final Ruby runtime;
65     private final int depthLimit;
66     private int depth = 0;
67     private MarshalCache cache;
68
69     private final static char TYPE_IVAR = 'I';
70     private final static char TYPE_USRMARSHAL = 'U';
71     private final static char TYPE_USERDEF = 'u';
72     private final static char TYPE_UCLASS = 'C';
73
74     public MarshalStream(Ruby runtime, OutputStream JavaDoc out, int depthLimit) throws IOException JavaDoc {
75         super(out);
76
77         this.runtime = runtime;
78         this.depthLimit = depthLimit >= 0 ? depthLimit : Integer.MAX_VALUE;
79         this.cache = new MarshalCache();
80
81         out.write(Constants.MARSHAL_MAJOR);
82         out.write(Constants.MARSHAL_MINOR);
83     }
84
85     public void dumpObject(IRubyObject value) throws IOException JavaDoc {
86         depth++;
87         
88         if (depth > depthLimit) {
89             throw runtime.newArgumentError("exceed depth limit");
90         }
91         
92         if (!shouldBeRegistered(value)) {
93             writeDirectly(value);
94         } else {
95             writeAndRegister(value);
96         }
97         depth--;
98         if (depth == 0) {
99             out.flush(); // flush afer whole dump is complete
100
}
101     }
102
103     private boolean shouldBeRegistered(IRubyObject value) {
104         if (value.isNil()) {
105             return false;
106         } else if (value instanceof RubyBoolean) {
107             return false;
108         } else if (value instanceof RubyFixnum) {
109             return false;
110         } else if (value instanceof RubyFloat) {
111             return false;
112         }
113         return true;
114     }
115
116     private void writeAndRegister(IRubyObject value) throws IOException JavaDoc {
117         if (cache.isRegistered(value)) {
118             cache.writeLink(this, value);
119         } else {
120             cache.register(value);
121             if (hasNewUserDefinedMarshaling(value)) {
122                 userNewMarshal(value);
123             } else if (hasUserDefinedMarshaling(value)) {
124                 userMarshal(value);
125             } else {
126                 writeDirectly(value);
127             }
128         }
129     }
130
131     private void writeDirectly(IRubyObject value) throws IOException JavaDoc {
132         Map JavaDoc instanceVariables = null;
133         
134         if (value.getNativeTypeIndex() != ClassIndex.OBJECT) {
135             if (value.safeHasInstanceVariables() && value.getNativeTypeIndex() != ClassIndex.CLASS) {
136                 // object has instance vars and isn't a class, get a snapshot to be marshalled
137
// and output the ivar header here
138

139                 instanceVariables = value.safeGetInstanceVariables();
140
141                 // write `I' instance var signet if class is NOT a direct subclass of Object
142
write(TYPE_IVAR);
143             }
144
145             if (value.getNativeTypeIndex() != value.getMetaClass().index) {
146                 // object is a custom class that extended one of the native types other than Object
147
writeUserClass(value);
148             }
149         } // Object's instance var logic is handled in the metaclass's marshal
150

151         writeObjectData(value);
152         
153         if (instanceVariables != null) {
154             dumpInstanceVars(instanceVariables);
155         }
156     }
157
158     private void writeObjectData(IRubyObject value) throws IOException JavaDoc {
159         // switch on the object's *native type*. This allows use-defined
160
// classes that have extended core native types to piggyback on their
161
// marshalling logic.
162
switch (value.getNativeTypeIndex()) {
163         case ClassIndex.ARRAY:
164             write('[');
165             RubyArray.marshalTo((RubyArray)value, this);
166             break;
167         case ClassIndex.FALSE:
168             write('F');
169             break;
170         case ClassIndex.FIXNUM: {
171             RubyFixnum fixnum = (RubyFixnum)value;
172             
173             if (fixnum.getLongValue() <= RubyFixnum.MAX_MARSHAL_FIXNUM) {
174                 write('i');
175                 writeInt((int) fixnum.getLongValue());
176                 break;
177             }
178             // FIXME: inefficient; constructing a bignum just for dumping?
179
value = RubyBignum.newBignum(value.getRuntime(), fixnum.getLongValue());
180             
181             // fall through
182
}
183         case ClassIndex.BIGNUM:
184             write('l');
185             RubyBignum.marshalTo((RubyBignum)value, this);
186             break;
187         case ClassIndex.CLASS:
188             write('c');
189             RubyClass.marshalTo((RubyClass)value, this);
190             break;
191         case ClassIndex.FLOAT:
192             write('f');
193             RubyFloat.marshalTo((RubyFloat)value, this);
194             break;
195         case ClassIndex.HASH: {
196             RubyHash hash = (RubyHash)value;
197             
198             if (hash.hasNonProcDefault()) {
199                 write('}');
200             } else {
201         write('{');
202             }
203             
204             RubyHash.marshalTo(hash, this);
205             break;
206         }
207         case ClassIndex.MODULE:
208             write('m');
209             RubyModule.marshalTo((RubyModule)value, this);
210             break;
211         case ClassIndex.NIL:
212             write('0');
213             break;
214         case ClassIndex.OBJECT:
215             dumpDefaultObjectHeader(value.getMetaClass());
216             value.getMetaClass().marshal(value, this);
217             break;
218         case ClassIndex.REGEXP:
219             write('/');
220             RubyRegexp.marshalTo((RubyRegexp)value, this);
221             break;
222         case ClassIndex.STRING:
223             write('"');
224             writeString(value.convertToString().getByteList());
225             break;
226         case ClassIndex.STRUCT:
227             write('S');
228             RubyStruct.marshalTo((RubyStruct)value, this);
229             break;
230         case ClassIndex.SYMBOL:
231             write(':');
232             writeString(value.toString());
233             break;
234         case ClassIndex.TRUE:
235             write('T');
236             break;
237         default:
238             dumpDefaultObjectHeader(value.getMetaClass());
239             value.getMetaClass().marshal(value, this);
240         }
241     }
242
243     private boolean hasNewUserDefinedMarshaling(IRubyObject value) {
244         return value.respondsTo("marshal_dump");
245     }
246
247     private void userNewMarshal(final IRubyObject value) throws IOException JavaDoc {
248         write(TYPE_USRMARSHAL);
249         RubyClass metaclass = value.getMetaClass();
250         while (metaclass.isSingleton()) {
251             metaclass = metaclass.getSuperClass();
252         }
253         dumpObject(RubySymbol.newSymbol(runtime, metaclass.getName()));
254
255         IRubyObject marshaled = value.callMethod(runtime.getCurrentContext(), "marshal_dump");
256         dumpObject(marshaled);
257     }
258
259     private boolean hasUserDefinedMarshaling(IRubyObject value) {
260         return value.respondsTo("_dump");
261     }
262
263     private void userMarshal(IRubyObject value) throws IOException JavaDoc {
264         write(TYPE_USERDEF);
265         RubyClass metaclass = value.getMetaClass();
266         while (metaclass.isSingleton()) {
267             metaclass = metaclass.getSuperClass();
268         }
269         dumpObject(RubySymbol.newSymbol(runtime, metaclass.getName()));
270
271         RubyString marshaled = (RubyString) value.callMethod(runtime.getCurrentContext(), "_dump", runtime.newFixnum(depthLimit));
272         writeString(marshaled.getByteList());
273     }
274     
275     public void writeUserClass(IRubyObject obj) throws IOException JavaDoc {
276         write(TYPE_UCLASS);
277         
278         // w_unique
279
if (obj.getMetaClass().getName().charAt(0) == '#') {
280             throw obj.getRuntime().newTypeError("Can't dump anonymous class");
281         }
282         
283         // w_symbol
284
dumpObject(runtime.newSymbol(obj.getMetaClass().getName()));
285     }
286     
287     public void dumpInstanceVars(Map JavaDoc instanceVars) throws IOException JavaDoc {
288         writeInt(instanceVars.size());
289         for (Iterator JavaDoc iter = instanceVars.keySet().iterator(); iter.hasNext();) {
290             String JavaDoc name = (String JavaDoc) iter.next();
291             IRubyObject value = (IRubyObject)instanceVars.get(name);
292
293             writeAndRegister(runtime.newSymbol(name));
294             dumpObject(value);
295         }
296     }
297     
298     public void dumpDefaultObjectHeader(RubyClass type) throws IOException JavaDoc {
299         write('o');
300         RubySymbol classname = RubySymbol.newSymbol(runtime, type.getName());
301         dumpObject(classname);
302     }
303
304     public void writeString(String JavaDoc value) throws IOException JavaDoc {
305         writeInt(value.length());
306         out.write(RubyString.stringToBytes(value));
307     }
308
309     public void writeString(ByteList value) throws IOException JavaDoc {
310         int len = value.length();
311         writeInt(len);
312         out.write(value.unsafeBytes(),0,len);
313     }
314
315     public void dumpSymbol(String JavaDoc value) throws IOException JavaDoc {
316         write(':');
317         writeInt(value.length());
318         out.write(RubyString.stringToBytes(value));
319     }
320
321     public void writeInt(int value) throws IOException JavaDoc {
322         if (value == 0) {
323             out.write(0);
324         } else if (0 < value && value < 123) {
325             out.write(value + 5);
326         } else if (-124 < value && value < 0) {
327             out.write((value - 5) & 0xff);
328         } else {
329             byte[] buf = new byte[4];
330             int i = 0;
331             for (; i < buf.length; i++) {
332                 buf[i] = (byte)(value & 0xff);
333                 
334                 value = value >> 8;
335                 if (value == 0 || value == -1) {
336                     break;
337                 }
338             }
339             int len = i + 1;
340             out.write(value < 0 ? -len : len);
341             out.write(buf, 0, i + 1);
342         }
343     }
344 }
345
Popular Tags