1 33 package org.jruby.runtime.marshal; 34 35 import java.io.FilterOutputStream ; 36 import java.io.IOException ; 37 import java.io.OutputStream ; 38 import java.util.Iterator ; 39 import java.util.Map ; 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 63 public class MarshalStream extends FilterOutputStream { 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 out, int depthLimit) throws IOException { 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 { 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(); } 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 { 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 { 132 Map instanceVariables = null; 133 134 if (value.getNativeTypeIndex() != ClassIndex.OBJECT) { 135 if (value.safeHasInstanceVariables() && value.getNativeTypeIndex() != ClassIndex.CLASS) { 136 139 instanceVariables = value.safeGetInstanceVariables(); 140 141 write(TYPE_IVAR); 143 } 144 145 if (value.getNativeTypeIndex() != value.getMetaClass().index) { 146 writeUserClass(value); 148 } 149 } 151 writeObjectData(value); 152 153 if (instanceVariables != null) { 154 dumpInstanceVars(instanceVariables); 155 } 156 } 157 158 private void writeObjectData(IRubyObject value) throws IOException { 159 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 value = RubyBignum.newBignum(value.getRuntime(), fixnum.getLongValue()); 180 181 } 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 { 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 { 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 { 276 write(TYPE_UCLASS); 277 278 if (obj.getMetaClass().getName().charAt(0) == '#') { 280 throw obj.getRuntime().newTypeError("Can't dump anonymous class"); 281 } 282 283 dumpObject(runtime.newSymbol(obj.getMetaClass().getName())); 285 } 286 287 public void dumpInstanceVars(Map instanceVars) throws IOException { 288 writeInt(instanceVars.size()); 289 for (Iterator iter = instanceVars.keySet().iterator(); iter.hasNext();) { 290 String name = (String ) 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 { 299 write('o'); 300 RubySymbol classname = RubySymbol.newSymbol(runtime, type.getName()); 301 dumpObject(classname); 302 } 303 304 public void writeString(String value) throws IOException { 305 writeInt(value.length()); 306 out.write(RubyString.stringToBytes(value)); 307 } 308 309 public void writeString(ByteList value) throws IOException { 310 int len = value.length(); 311 writeInt(len); 312 out.write(value.unsafeBytes(),0,len); 313 } 314 315 public void dumpSymbol(String value) throws IOException { 316 write(':'); 317 writeInt(value.length()); 318 out.write(RubyString.stringToBytes(value)); 319 } 320 321 public void writeInt(int value) throws IOException { 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 |