001: /***** BEGIN LICENSE BLOCK *****
002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
003: *
004: * The contents of this file are subject to the Common Public
005: * License Version 1.0 (the "License"); you may not use this file
006: * except in compliance with the License. You may obtain a copy of
007: * the License at http://www.eclipse.org/legal/cpl-v10.html
008: *
009: * Software distributed under the License is distributed on an "AS
010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
011: * implied. See the License for the specific language governing
012: * rights and limitations under the License.
013: *
014: * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
015: * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
016: * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
017: * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
018: * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
019: * Copyright (C) 2006 Ola Bini <ola.bini@ki.se>
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * either of the GNU General Public License Version 2 or later (the "GPL"),
023: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
024: * in which case the provisions of the GPL or the LGPL are applicable instead
025: * of those above. If you wish to allow use of your version of this file only
026: * under the terms of either the GPL or the LGPL, and not to allow others to
027: * use your version of this file under the terms of the CPL, indicate your
028: * decision by deleting the provisions above and replace them with the notice
029: * and other provisions required by the GPL or the LGPL. If you do not delete
030: * the provisions above, a recipient may use your version of this file under
031: * the terms of any one of the CPL, the GPL or the LGPL.
032: ***** END LICENSE BLOCK *****/package org.jruby.runtime.marshal;
033:
034: import java.io.FilterOutputStream;
035: import java.io.IOException;
036: import java.io.OutputStream;
037: import java.util.Iterator;
038: import java.util.Map;
039: import org.jruby.Ruby;
040: import org.jruby.RubyArray;
041: import org.jruby.RubyBignum;
042: import org.jruby.RubyBoolean;
043: import org.jruby.RubyClass;
044: import org.jruby.RubyFixnum;
045: import org.jruby.RubyFloat;
046: import org.jruby.RubyHash;
047: import org.jruby.RubyModule;
048: import org.jruby.RubyRegexp;
049: import org.jruby.RubyString;
050: import org.jruby.RubyStruct;
051: import org.jruby.RubySymbol;
052: import org.jruby.IncludedModuleWrapper;
053: import org.jruby.runtime.ClassIndex;
054: import org.jruby.runtime.Constants;
055: import org.jruby.runtime.builtin.IRubyObject;
056: import org.jruby.util.ByteList;
057: import org.jruby.internal.runtime.methods.DynamicMethod;
058:
059: /**
060: * Marshals objects into Ruby's binary marshal format.
061: *
062: * @author Anders
063: */
064: public class MarshalStream extends FilterOutputStream {
065: private final Ruby runtime;
066: private final int depthLimit;
067: private int depth = 0;
068: private MarshalCache cache;
069:
070: private final static char TYPE_IVAR = 'I';
071: private final static char TYPE_USRMARSHAL = 'U';
072: private final static char TYPE_USERDEF = 'u';
073: private final static char TYPE_UCLASS = 'C';
074:
075: public MarshalStream(Ruby runtime, OutputStream out, int depthLimit)
076: throws IOException {
077: super (out);
078:
079: this .runtime = runtime;
080: this .depthLimit = depthLimit >= 0 ? depthLimit
081: : Integer.MAX_VALUE;
082: this .cache = new MarshalCache();
083:
084: out.write(Constants.MARSHAL_MAJOR);
085: out.write(Constants.MARSHAL_MINOR);
086: }
087:
088: public void dumpObject(IRubyObject value) throws IOException {
089: depth++;
090:
091: if (depth > depthLimit) {
092: throw runtime.newArgumentError("exceed depth limit");
093: }
094:
095: if (!shouldBeRegistered(value)) {
096: writeDirectly(value);
097: } else {
098: writeAndRegister(value);
099: }
100: depth--;
101: if (depth == 0) {
102: out.flush(); // flush afer whole dump is complete
103: }
104: }
105:
106: private boolean shouldBeRegistered(IRubyObject value) {
107: if (value.isNil()) {
108: return false;
109: } else if (value instanceof RubyBoolean) {
110: return false;
111: } else if (value instanceof RubyFixnum) {
112: return false;
113: }
114: return true;
115: }
116:
117: private void writeAndRegister(IRubyObject value) throws IOException {
118: if (cache.isRegistered(value)) {
119: cache.writeLink(this , value);
120: } else {
121: cache.register(value);
122: if (hasNewUserDefinedMarshaling(value)) {
123: userNewMarshal(value);
124: } else if (hasUserDefinedMarshaling(value)) {
125: userMarshal(value);
126: } else {
127: writeDirectly(value);
128: }
129: }
130: }
131:
132: private Map getInstanceVariables(IRubyObject value)
133: throws IOException {
134: Map instanceVariables = null;
135: if (value.getNativeTypeIndex() != ClassIndex.OBJECT) {
136: if (!value.isImmediate()
137: && value.safeHasInstanceVariables()
138: && value.getNativeTypeIndex() != ClassIndex.CLASS) {
139: // object has instance vars and isn't a class, get a snapshot to be marshalled
140: // and output the ivar header here
141:
142: instanceVariables = value.safeGetInstanceVariables();
143:
144: // write `I' instance var signet if class is NOT a direct subclass of Object
145: write(TYPE_IVAR);
146: }
147: RubyClass type = value.getMetaClass();
148: switch (value.getNativeTypeIndex()) {
149: case ClassIndex.STRING:
150: case ClassIndex.REGEXP:
151: case ClassIndex.ARRAY:
152: case ClassIndex.HASH:
153: type = dumpExtended(type);
154: break;
155: }
156:
157: if (value.getNativeTypeIndex() != value.getMetaClass().index
158: && value.getNativeTypeIndex() != ClassIndex.STRUCT) {
159: // object is a custom class that extended one of the native types other than Object
160: writeUserClass(value, type);
161: }
162: }
163: return instanceVariables;
164: }
165:
166: private void writeDirectly(IRubyObject value) throws IOException {
167: Map instanceVariables = getInstanceVariables(value);
168: writeObjectData(value);
169: if (instanceVariables != null) {
170: dumpInstanceVars(instanceVariables);
171: }
172: }
173:
174: private void writeObjectData(IRubyObject value) throws IOException {
175: // switch on the object's *native type*. This allows use-defined
176: // classes that have extended core native types to piggyback on their
177: // marshalling logic.
178: switch (value.getNativeTypeIndex()) {
179: case ClassIndex.ARRAY:
180: write('[');
181: RubyArray.marshalTo((RubyArray) value, this );
182: break;
183: case ClassIndex.FALSE:
184: write('F');
185: break;
186: case ClassIndex.FIXNUM: {
187: RubyFixnum fixnum = (RubyFixnum) value;
188:
189: if (fixnum.getLongValue() <= RubyFixnum.MAX_MARSHAL_FIXNUM
190: && fixnum.getLongValue() >= RubyFixnum.MIN_MARSHAL_FIXNUM) {
191: write('i');
192: writeInt((int) fixnum.getLongValue());
193: break;
194: }
195: // FIXME: inefficient; constructing a bignum just for dumping?
196: value = RubyBignum.newBignum(value.getRuntime(), fixnum
197: .getLongValue());
198:
199: // fall through
200: }
201: case ClassIndex.BIGNUM:
202: write('l');
203: RubyBignum.marshalTo((RubyBignum) value, this );
204: break;
205: case ClassIndex.CLASS:
206: write('c');
207: RubyClass.marshalTo((RubyClass) value, this );
208: break;
209: case ClassIndex.FLOAT:
210: write('f');
211: RubyFloat.marshalTo((RubyFloat) value, this );
212: break;
213: case ClassIndex.HASH: {
214: RubyHash hash = (RubyHash) value;
215:
216: if (hash.getIfNone().isNil()) {
217: write('{');
218: } else if (hash.hasDefaultProc()) {
219: throw hash.getRuntime().newTypeError(
220: "can't dump hash with default proc");
221: } else {
222: write('}');
223: }
224:
225: RubyHash.marshalTo(hash, this );
226: break;
227: }
228: case ClassIndex.MODULE:
229: write('m');
230: RubyModule.marshalTo((RubyModule) value, this );
231: break;
232: case ClassIndex.NIL:
233: write('0');
234: break;
235: case ClassIndex.OBJECT:
236: dumpDefaultObjectHeader(value.getMetaClass());
237: value.getMetaClass().marshal(value, this );
238: break;
239: case ClassIndex.REGEXP:
240: write('/');
241: RubyRegexp.marshalTo((RubyRegexp) value, this );
242: break;
243: case ClassIndex.STRING:
244: write('"');
245: writeString(value.convertToString().getByteList());
246: break;
247: case ClassIndex.STRUCT:
248: // write('S');
249: RubyStruct.marshalTo((RubyStruct) value, this );
250: break;
251: case ClassIndex.SYMBOL:
252: write(':');
253: writeString(value.toString());
254: break;
255: case ClassIndex.TRUE:
256: write('T');
257: break;
258: default:
259: dumpDefaultObjectHeader(value.getMetaClass());
260: value.getMetaClass().marshal(value, this );
261: }
262:
263: }
264:
265: private boolean hasNewUserDefinedMarshaling(IRubyObject value) {
266: return value.respondsTo("marshal_dump");
267: }
268:
269: private void userNewMarshal(final IRubyObject value)
270: throws IOException {
271: write(TYPE_USRMARSHAL);
272: RubyClass metaclass = value.getMetaClass();
273: while (metaclass.isSingleton()) {
274: metaclass = metaclass.getSuperClass();
275: }
276: dumpObject(RubySymbol.newSymbol(runtime, metaclass.getName()));
277:
278: IRubyObject marshaled = value.callMethod(runtime
279: .getCurrentContext(), "marshal_dump");
280: dumpObject(marshaled);
281: }
282:
283: private boolean hasUserDefinedMarshaling(IRubyObject value) {
284: return value.respondsTo("_dump");
285: }
286:
287: private void userMarshal(IRubyObject value) throws IOException {
288: RubyString marshaled = (RubyString) value.callMethod(runtime
289: .getCurrentContext(), "_dump", runtime
290: .newFixnum(depthLimit));
291:
292: Map instanceVariables = marshaled.safeGetInstanceVariables();
293: if (instanceVariables != null) {
294: write(TYPE_IVAR);
295: }
296:
297: write(TYPE_USERDEF);
298: RubyClass metaclass = value.getMetaClass();
299: while (metaclass.isSingleton()) {
300: metaclass = metaclass.getSuperClass();
301: }
302:
303: dumpObject(RubySymbol.newSymbol(runtime, metaclass.getName()));
304:
305: writeString(marshaled.getByteList());
306: if (instanceVariables != null) {
307: dumpInstanceVars(instanceVariables);
308: }
309: }
310:
311: public void writeUserClass(IRubyObject obj, RubyClass type)
312: throws IOException {
313: write(TYPE_UCLASS);
314:
315: // w_unique
316: if (type.getName().charAt(0) == '#') {
317: throw obj.getRuntime().newTypeError(
318: "Can't dump anonymous class");
319: }
320:
321: // w_symbol
322: dumpObject(runtime.newSymbol(type.getName()));
323: }
324:
325: public void dumpInstanceVars(Map instanceVars) throws IOException {
326: writeInt(instanceVars.size());
327: for (Iterator iter = instanceVars.keySet().iterator(); iter
328: .hasNext();) {
329: String name = (String) iter.next();
330: IRubyObject value = (IRubyObject) instanceVars.get(name);
331: writeAndRegister(runtime.newSymbol(name));
332: dumpObject(value);
333: }
334: }
335:
336: private boolean hasSingletonMethods(RubyClass type) {
337: for (Iterator iter = type.getMethods().entrySet().iterator(); iter
338: .hasNext();) {
339: Map.Entry entry = (Map.Entry) iter.next();
340: DynamicMethod method = (DynamicMethod) entry.getValue();
341: // We do not want to capture cached methods
342: if (method.getImplementationClass() == type) {
343: return true;
344: }
345: }
346: return false;
347: }
348:
349: /** w_extended
350: *
351: */
352: private RubyClass dumpExtended(RubyClass type) throws IOException {
353: if (type.isSingleton()) {
354: if (hasSingletonMethods(type)
355: || type.getInstanceVariables().size() > 1) {
356: throw type.getRuntime().newTypeError(
357: "singleton can't be dumped");
358: }
359: type = type.getSuperClass();
360: }
361: while (type.isIncluded()) {
362: write('e');
363: dumpObject(RubySymbol.newSymbol(runtime,
364: ((IncludedModuleWrapper) type)
365: .getNonIncludedClass().getName()));
366: type = type.getSuperClass();
367: }
368: return type;
369: }
370:
371: public void dumpDefaultObjectHeader(RubyClass type)
372: throws IOException {
373: dumpDefaultObjectHeader('o', type);
374: }
375:
376: public void dumpDefaultObjectHeader(char tp, RubyClass type)
377: throws IOException {
378: dumpExtended(type);
379: write(tp);
380: RubySymbol classname = RubySymbol.newSymbol(runtime, type
381: .getRealClass().getName());
382: dumpObject(classname);
383: }
384:
385: public void writeString(String value) throws IOException {
386: writeInt(value.length());
387: out.write(RubyString.stringToBytes(value));
388: }
389:
390: public void writeString(ByteList value) throws IOException {
391: int len = value.length();
392: writeInt(len);
393: out.write(value.unsafeBytes(), value.begin(), len);
394: }
395:
396: public void dumpSymbol(String value) throws IOException {
397: write(':');
398: writeInt(value.length());
399: out.write(RubyString.stringToBytes(value));
400: }
401:
402: public void writeInt(int value) throws IOException {
403: if (value == 0) {
404: out.write(0);
405: } else if (0 < value && value < 123) {
406: out.write(value + 5);
407: } else if (-124 < value && value < 0) {
408: out.write((value - 5) & 0xff);
409: } else {
410: byte[] buf = new byte[4];
411: int i = 0;
412: for (; i < buf.length; i++) {
413: buf[i] = (byte) (value & 0xff);
414:
415: value = value >> 8;
416: if (value == 0 || value == -1) {
417: break;
418: }
419: }
420: int len = i + 1;
421: out.write(value < 0 ? -len : len);
422: out.write(buf, 0, i + 1);
423: }
424: }
425: }
|