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.BufferedInputStream;
035: import java.io.IOException;
036: import java.io.InputStream;
037: import org.jruby.Ruby;
038: import org.jruby.RubyArray;
039: import org.jruby.RubyBignum;
040: import org.jruby.RubyClass;
041: import org.jruby.RubyFixnum;
042: import org.jruby.RubyFloat;
043: import org.jruby.RubyHash;
044: import org.jruby.RubyModule;
045: import org.jruby.RubyRegexp;
046: import org.jruby.RubyString;
047: import org.jruby.RubyStruct;
048: import org.jruby.RubySymbol;
049: import org.jruby.exceptions.RaiseException;
050: import org.jruby.runtime.Block;
051: import org.jruby.runtime.builtin.IRubyObject;
052: import org.jruby.util.ByteList;
053:
054: /**
055: * Unmarshals objects from strings or streams in Ruby's marsal format.
056: *
057: * @author Anders
058: */
059: public class UnmarshalStream extends BufferedInputStream {
060: protected final Ruby runtime;
061: private UnmarshalCache cache;
062: private IRubyObject proc;
063:
064: public UnmarshalStream(Ruby runtime, InputStream in,
065: IRubyObject proc) throws IOException {
066: super (in);
067: this .runtime = runtime;
068: this .cache = new UnmarshalCache(runtime);
069: this .proc = proc;
070:
071: in.read(); // Major
072: in.read(); // Minor
073: }
074:
075: public IRubyObject unmarshalObject() throws IOException {
076: int type = readUnsignedByte();
077: IRubyObject result;
078: if (cache.isLinkType(type)) {
079: result = cache.readLink(this , type);
080: } else {
081: result = unmarshalObjectDirectly(type);
082: }
083: return result;
084: }
085:
086: public void registerLinkTarget(IRubyObject newObject) {
087: cache.register(newObject);
088: }
089:
090: private IRubyObject unmarshalObjectDirectly(int type)
091: throws IOException {
092: IRubyObject rubyObj = null;
093: switch (type) {
094: case 'I':
095: rubyObj = unmarshalObject();
096: defaultInstanceVarsUnmarshal(rubyObj);
097: break;
098: case '0':
099: rubyObj = runtime.getNil();
100: break;
101: case 'T':
102: rubyObj = runtime.getTrue();
103: break;
104: case 'F':
105: rubyObj = runtime.getFalse();
106: break;
107: case '"':
108: rubyObj = RubyString.unmarshalFrom(this );
109: break;
110: case 'i':
111: rubyObj = RubyFixnum.unmarshalFrom(this );
112: break;
113: case 'f':
114: rubyObj = RubyFloat.unmarshalFrom(this );
115: break;
116: case '/':
117: rubyObj = RubyRegexp.unmarshalFrom(this );
118: break;
119: case ':':
120: rubyObj = RubySymbol.unmarshalFrom(this );
121: break;
122: case '[':
123: rubyObj = RubyArray.unmarshalFrom(this );
124: break;
125: case '{':
126: rubyObj = RubyHash.unmarshalFrom(this , false);
127: break;
128: case '}':
129: // "hashdef" object, a hash with a default
130: rubyObj = RubyHash.unmarshalFrom(this , true);
131: break;
132: case 'c':
133: rubyObj = RubyClass.unmarshalFrom(this );
134: break;
135: case 'm':
136: rubyObj = RubyModule.unmarshalFrom(this );
137: break;
138: case 'e':
139: RubySymbol moduleName = (RubySymbol) unmarshalObject();
140: RubyModule tp = null;
141: try {
142: tp = runtime.getClassFromPath(moduleName.asSymbol());
143: } catch (RaiseException e) {
144: if (e.getException().isKindOf(
145: runtime.getModule("NameError"))) {
146: throw runtime
147: .newArgumentError("undefined class/module "
148: + moduleName.asSymbol());
149: }
150: throw e;
151: }
152:
153: rubyObj = unmarshalObject();
154:
155: tp.extend_object(rubyObj);
156: tp.callMethod(runtime.getCurrentContext(), "extended",
157: rubyObj);
158: break;
159: case 'l':
160: rubyObj = RubyBignum.unmarshalFrom(this );
161: break;
162: case 'S':
163: rubyObj = RubyStruct.unmarshalFrom(this );
164: break;
165: case 'o':
166: rubyObj = defaultObjectUnmarshal();
167: break;
168: case 'u':
169: rubyObj = userUnmarshal();
170: break;
171: case 'U':
172: rubyObj = userNewUnmarshal();
173: break;
174: case 'C':
175: rubyObj = uclassUnmarshall();
176: break;
177: default:
178: throw getRuntime().newArgumentError(
179: "dump format error(" + (char) type + ")");
180: }
181:
182: if (proc != null && type != ':') {
183: // call the proc, but not for symbols
184: proc.callMethod(getRuntime().getCurrentContext(), "call",
185: new IRubyObject[] { rubyObj });
186: }
187: return rubyObj;
188: }
189:
190: public Ruby getRuntime() {
191: return runtime;
192: }
193:
194: public int readUnsignedByte() throws IOException {
195: int result = read();
196: if (result == -1) {
197: throw new IOException("Unexpected end of stream");
198: }
199: return result;
200: }
201:
202: public byte readSignedByte() throws IOException {
203: int b = readUnsignedByte();
204: if (b > 127) {
205: return (byte) (b - 256);
206: }
207: return (byte) b;
208: }
209:
210: public ByteList unmarshalString() throws IOException {
211: int length = unmarshalInt();
212: byte[] buffer = new byte[length];
213:
214: // FIXME: sooper inefficient, but it's working better...
215: int b = 0;
216: int i = 0;
217: while (i < length && (b = read()) != -1) {
218: buffer[i++] = (byte) b;
219: }
220: if (i < length) {
221: throw new IOException("Unexpected end of stream");
222: }
223: return new ByteList(buffer, false);
224: }
225:
226: public int unmarshalInt() throws IOException {
227: int c = readSignedByte();
228: if (c == 0) {
229: return 0;
230: } else if (5 < c && c < 128) {
231: return c - 5;
232: } else if (-129 < c && c < -5) {
233: return c + 5;
234: }
235: long result;
236: if (c > 0) {
237: result = 0;
238: for (int i = 0; i < c; i++) {
239: result |= (long) readUnsignedByte() << (8 * i);
240: }
241: } else {
242: c = -c;
243: result = -1;
244: for (int i = 0; i < c; i++) {
245: result &= ~((long) 0xff << (8 * i));
246: result |= (long) readUnsignedByte() << (8 * i);
247: }
248: }
249: return (int) result;
250: }
251:
252: private IRubyObject defaultObjectUnmarshal() throws IOException {
253: RubySymbol className = (RubySymbol) unmarshalObject();
254:
255: RubyClass type = null;
256: try {
257: type = (RubyClass) runtime.getClassFromPath(className
258: .asSymbol());
259: } catch (RaiseException e) {
260: if (e.getException().isKindOf(
261: runtime.getModule("NameError"))) {
262: throw runtime
263: .newArgumentError("undefined class/module "
264: + className.asSymbol());
265: }
266:
267: throw e;
268: }
269:
270: assert type != null : "type shouldn't be null.";
271:
272: IRubyObject result = (IRubyObject) type.unmarshal(this );
273:
274: return result;
275: }
276:
277: public void defaultInstanceVarsUnmarshal(IRubyObject object)
278: throws IOException {
279: int count = unmarshalInt();
280:
281: for (int i = 0; i < count; i++) {
282: String name = unmarshalObject().asSymbol();
283: IRubyObject value = unmarshalObject();
284: object.setInstanceVariable(name, value);
285: }
286: }
287:
288: private IRubyObject uclassUnmarshall() throws IOException {
289: RubySymbol className = (RubySymbol) unmarshalObject();
290:
291: RubyClass type = (RubyClass) runtime.getClassFromPath(className
292: .asSymbol());
293:
294: IRubyObject result = unmarshalObject();
295:
296: result.setMetaClass(type);
297:
298: return result;
299: }
300:
301: private IRubyObject userUnmarshal() throws IOException {
302: String className = unmarshalObject().asSymbol();
303: ByteList marshaled = unmarshalString();
304: RubyModule classInstance;
305: try {
306: classInstance = runtime.getClassFromPath(className);
307: } catch (RaiseException e) {
308: if (e.getException().isKindOf(
309: runtime.getModule("NameError"))) {
310: throw runtime
311: .newArgumentError("undefined class/module "
312: + className);
313: }
314:
315: throw e;
316: }
317: if (!classInstance.respondsTo("_load")) {
318: throw runtime.newTypeError("class "
319: + classInstance.getName()
320: + " needs to have method `_load'");
321: }
322: IRubyObject result = classInstance.callMethod(getRuntime()
323: .getCurrentContext(), "_load", RubyString.newString(
324: getRuntime(), marshaled));
325: registerLinkTarget(result);
326: return result;
327: }
328:
329: private IRubyObject userNewUnmarshal() throws IOException {
330: String className = unmarshalObject().asSymbol();
331: IRubyObject marshaled = unmarshalObject();
332: RubyClass classInstance = runtime.getClass(className);
333: IRubyObject result = classInstance.newInstance(
334: new IRubyObject[0], Block.NULL_BLOCK);
335: result.callMethod(getRuntime().getCurrentContext(),
336: "marshal_load", marshaled);
337: registerLinkTarget(result);
338: return result;
339: }
340: }
|