javaobj 0.1 → 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/lib/javaobs.rb +200 -113
  2. data/rakefile +4 -3
  3. metadata +1 -1
@@ -1,11 +1,60 @@
1
1
 
2
2
  require 'delegate'
3
3
 
4
+ # Java Objects namespace to read and write Java serialized objects to streams.
5
+ # Any Java serialized object can be read from a stream. To write a Java object,
6
+ # the meta class must be primed with a sample input serialized object. The is
7
+ # required because Java uses a UUID to identify classes and it is generated using
8
+ # a complex hashing scheme of data and method signatures. Since this system does
9
+ # not have access to that information, it needs to get it from a serialized object.
10
+ #
11
+ # Objects that have custom serialization methods can be read and written by
12
+ # creating a class as we have for the Date class:
13
+ #
14
+ # class Date < SimpleDelegator
15
+ # extend JavaObject
16
+ #
17
+ # def initialize
18
+ # super(Time)
19
+ # end
20
+ #
21
+ # def time=(time)
22
+ # case time
23
+ # when Time
24
+ # __setobj__(time)
25
+ #
26
+ # when String
27
+ # t, = time.unpack("Q")
28
+ # __setobj__(Time.at(t / 1000, (t % 1000) * 1000))
29
+ # end
30
+ # end
31
+ #
32
+ # def data
33
+ # t = __getobj__.tv_sec * 1000 + __getobj__.tv_usec / 1000
34
+ # [t].pack("Q")
35
+ # end
36
+ # end
37
+ #
38
+ # The important methods are the data method that is used for writing the
39
+ # the object to a stream.
40
+ #
41
+ # All other classes will be auto-generated when the stream is read and persisted.
42
+ # A Java Meta Class is added to the Ruby Class that contains all the Java field
43
+ # information needed to serialize the objects.
44
+
4
45
  module Java
46
+
47
+ # An error thrown when a serialization error occurs.
48
+ class SerializationError < RuntimeError
49
+ end
50
+
51
+ # An representation of a Java instance variable used to serialize
52
+ # data to and from a stream.
5
53
  class JavaField
6
54
  attr_reader :name, :type
7
55
  attr_accessor :subtype
8
56
 
57
+ # Create a java instance variable with a name and data type.
9
58
  def initialize(name, type)
10
59
  @name = name
11
60
  @type = type
@@ -17,6 +66,9 @@ module Java
17
66
  end
18
67
  end
19
68
 
69
+ # The Java meta class with all the information needed for
70
+ # serialization of the Ruby class to the stream. This class
71
+ # is attached to the Ruby class.
20
72
  class JavaClass
21
73
  attr_accessor :superClass, :rubyClass
22
74
  attr_reader :name, :uid, :fields, :javaName, :flags, :arrayType
@@ -31,6 +83,7 @@ module Java
31
83
  @arrayType = name[1] if name[0] == ?[
32
84
  end
33
85
 
86
+ # Add a field to the class.
34
87
  def addField(field)
35
88
  @fields << field
36
89
  end
@@ -40,19 +93,24 @@ module Java
40
93
  end
41
94
  end
42
95
 
96
+ # A small mixin to extend a Ruby class with the associated
97
+ # JavaClass.
43
98
  module JavaObject
44
99
  def javaClass
45
100
  @javaClass
46
101
  end
47
102
  end
48
103
 
104
+ # The custom serialization of the Java Date class
49
105
  class Date < SimpleDelegator
50
106
  extend JavaObject
51
107
 
52
108
  def initialize
53
109
  super(Time)
54
110
  end
55
-
111
+
112
+ # Set the time with a Time object or a Java binary serialized
113
+ # date.
56
114
  def time=(time)
57
115
  case time
58
116
  when Time
@@ -64,16 +122,19 @@ module Java
64
122
  end
65
123
  end
66
124
 
125
+ # Get the data in the form needed for the Java date serialization.
67
126
  def data
68
127
  t = __getobj__.tv_sec * 1000 + __getobj__.tv_usec / 1000
69
128
  [t].pack("Q")
70
129
  end
71
130
  end
72
131
 
132
+ # The Java Array wrapper using an Ruby Array.
73
133
  class JavaArray < Array
74
134
  extend JavaObject
75
135
  end
76
136
 
137
+ # Container for the Java serialization constants.
77
138
  module ObjectStream
78
139
  STREAM_MAGIC = 0xACED
79
140
  STREAM_VERSION = 5
@@ -111,6 +172,8 @@ module Java
111
172
  PRIM_OBJECT = 76 # 'L'
112
173
  end
113
174
 
175
+ # The Ruby version of the Java ObjectInputStream. Creates a Ruby
176
+ # proxy class for each Java Class.
114
177
  class ObjectInputStream
115
178
  include ObjectStream
116
179
 
@@ -125,6 +188,7 @@ module Java
125
188
  def readUID; @str.read(8); end
126
189
  def readLong; @str.read(8).unpack("Q").first; end
127
190
 
191
+ # Read a Java block of data with a size and then the following data.
128
192
  def readBlockData
129
193
  byte = readByte
130
194
  size = nil
@@ -136,15 +200,16 @@ module Java
136
200
  size = readInt
137
201
 
138
202
  else
139
- raise "Expecting TC_BLOCKDATA" unless byte == TC_BLOCKDATA
203
+ raise SerializationError, "Expecting TC_BLOCKDATA" unless byte == TC_BLOCKDATA
140
204
  end
141
205
 
142
206
  data = @str.read(size)
143
207
  byte = readByte
144
- raise "Unexpected byte #{byte}" unless byte == TC_ENDBLOCKDATA
208
+ raise SerializationError, "Unexpected byte #{byte}" unless byte == TC_ENDBLOCKDATA
145
209
  data
146
210
  end
147
211
 
212
+ # Read all the fields from the stream and add them to the class.
148
213
  def readFields(klass)
149
214
  fieldCount = readShort
150
215
  1.upto(fieldCount) do
@@ -160,11 +225,14 @@ module Java
160
225
  end
161
226
  end
162
227
 
228
+ # Read the class annotation. We do not currently handle annotations.
163
229
  def readClassAnnotation
164
230
  ebd = readByte
165
- raise "We do not handle annotations!" unless ebd == TC_ENDBLOCKDATA
231
+ raise SerializationError, "We do not handle annotations!" unless ebd == TC_ENDBLOCKDATA
166
232
  end
167
233
 
234
+ # Read the Java class description and create a Ruby class to proxy it
235
+ # in this name-space. Added special handling for the Date class.
168
236
  def readClassDesc
169
237
  name = readString
170
238
  uid = readUID
@@ -215,6 +283,7 @@ module Java
215
283
  klass
216
284
  end
217
285
 
286
+ # Read an array of objects.
218
287
  def readArray(klass)
219
288
  size = readInt
220
289
  a = klass.rubyClass.new
@@ -225,6 +294,7 @@ module Java
225
294
  a
226
295
  end
227
296
 
297
+ # Read a primitive data type.
228
298
  def readType(type, arrayType = nil, field = nil)
229
299
  case type
230
300
  when PRIM_BYTE
@@ -255,10 +325,11 @@ module Java
255
325
  readObject
256
326
 
257
327
  else
258
- raise "Unknown type #{type}"
328
+ raise SerializationError, "Unknown type #{type}"
259
329
  end
260
330
  end
261
331
 
332
+ # Read class data and recursively read parent classes.
262
333
  def readClassData(klass, object = nil)
263
334
  # Handle special case for Date
264
335
  if object == nil
@@ -285,6 +356,7 @@ module Java
285
356
  object
286
357
  end
287
358
 
359
+ # Read an object from the stream.
288
360
  def readObject
289
361
  object = nil
290
362
  byte = readByte
@@ -313,12 +385,13 @@ module Java
313
385
  object = nil
314
386
 
315
387
  else
316
- raise "Unexpected byte #{byte} at #{@str.pos}"
388
+ raise SerializationError, "Unexpected byte #{byte} at #{@str.pos}"
317
389
  end
318
390
 
319
391
  object
320
392
  end
321
393
 
394
+ # Initialize from a stream.
322
395
  def initialize(str)
323
396
  @str = str
324
397
  magic = readUShort
@@ -330,6 +403,8 @@ module Java
330
403
 
331
404
  end
332
405
 
406
+ # Read all objects in the stream. Calls readObject until the stream
407
+ # eof is reached.
333
408
  def readObjects
334
409
  objs = []
335
410
  until (@str.eof)
@@ -339,6 +414,8 @@ module Java
339
414
  end
340
415
  end
341
416
 
417
+ # A Ruby version of the Java ObjectOutputStream. The Ruby classes must
418
+ # have attached Java meta classes to attach UUID.
342
419
  class ObjectOutputStream
343
420
  include ObjectStream
344
421
 
@@ -353,99 +430,108 @@ module Java
353
430
  def writeUID(u); @str.write u; end
354
431
  def writeLong(l); @str.write [l].pack("Q"); end
355
432
 
356
- def nextHandle
357
- h = @nextHandle
358
- @nextHandle += 1
359
- h
360
- end
361
-
362
- def writeArray(klass, v)
363
- type = klass.arrayType
364
- writeInt(v.length)
365
- v.each do |e|
366
- writeType(type, e)
367
- end
368
- end
369
-
370
- def writeBlockData(data)
371
- if (data.length <= 255)
372
- writeByte(TC_BLOCKDATA)
373
- writeByte(data.length)
374
- else
375
- writeByte(TC_BLOCKDATALONG)
376
- writeInt(data.length)
377
- end
433
+ # Creates object reference handles.
434
+ def nextHandle
435
+ h = @nextHandle
436
+ @nextHandle += 1
437
+ h
438
+ end
378
439
 
379
- @str.write data
380
- writeByte(TC_ENDBLOCKDATA)
381
- end
440
+ # Write an array of objects.
441
+ def writeArray(klass, v)
442
+ type = klass.arrayType
443
+ writeInt(v.length)
444
+ v.each do |e|
445
+ writeType(type, e)
446
+ end
447
+ end
382
448
 
383
- def writeType(type, v)
384
- case type
385
- when PRIM_BYTE
386
- writeByte(v)
387
-
388
- when PRIM_CHAR
389
- writeByte(v)
390
-
391
- when PRIM_DOUBLE
392
- writeDouble(v)
393
-
394
- when PRIM_FLOAT
395
- writeFloat(v)
396
-
397
- when PRIM_INT
398
- writeInt(v)
399
-
400
- when PRIM_LONG
401
- writeLong(v)
402
-
403
- when PRIM_SHORT
404
- writeShort(v)
405
-
406
- when PRIM_BOOL
407
- writeBool(v)
408
-
409
- when PRIM_OBJECT, PRIM_ARRAY
410
- writeObject(v)
411
-
412
- else
413
- raise "Unknown type #{type}"
414
- end
415
- end
416
-
417
- def writeClassDesc(klass)
418
- @handles[klass] = nextHandle
419
-
420
- writeString klass.javaName
421
- writeUID klass.uid
422
- writeByte klass.flags
423
-
424
- writeShort(klass.fields.length)
425
- klass.fields.each do |f|
426
- writeByte(f.type)
427
- writeString(f.name)
428
- writeObject(f.subtype) if f.subtype
429
- end
449
+ # Writes a block of data to the stream.
450
+ def writeBlockData(data)
451
+ if (data.length <= 255)
452
+ writeByte(TC_BLOCKDATA)
453
+ writeByte(data.length)
454
+ else
455
+ writeByte(TC_BLOCKDATALONG)
456
+ writeInt(data.length)
457
+ end
430
458
 
431
- writeByte(TC_ENDBLOCKDATA) # Annotations
432
- writeObject(klass.superClass)
433
- end
434
-
435
- def writeObjectData(klass, obj)
436
- writeObjectData(klass.superClass, obj) if klass.superClass
437
-
438
- if klass.flags == SC_SERIALIZABLE
439
- klass.fields.each do |f|
440
- v = obj.send(f.name.intern)
441
- writeType(f.type, v)
442
- end
443
- else
444
- writeBlockData(obj.data)
445
- end
446
- end
459
+ @str.write data
460
+ writeByte(TC_ENDBLOCKDATA)
461
+ end
462
+
463
+ # Reads a Java primitive type.
464
+ def writeType(type, v)
465
+ case type
466
+ when PRIM_BYTE
467
+ writeByte(v)
468
+
469
+ when PRIM_CHAR
470
+ writeByte(v)
471
+
472
+ when PRIM_DOUBLE
473
+ writeDouble(v)
474
+
475
+ when PRIM_FLOAT
476
+ writeFloat(v)
477
+
478
+ when PRIM_INT
479
+ writeInt(v)
480
+
481
+ when PRIM_LONG
482
+ writeLong(v)
483
+
484
+ when PRIM_SHORT
485
+ writeShort(v)
486
+
487
+ when PRIM_BOOL
488
+ writeBool(v)
489
+
490
+ when PRIM_OBJECT, PRIM_ARRAY
491
+ writeObject(v)
492
+
493
+ else
494
+ raise SerializationError, "Unknown type #{type}"
495
+ end
496
+ end
497
+
498
+ # Writes the class description to the stream.
499
+ def writeClassDesc(klass)
500
+ @handles[klass] = nextHandle
501
+
502
+ writeString klass.javaName
503
+ writeUID klass.uid
504
+ writeByte klass.flags
505
+
506
+ writeShort(klass.fields.length)
507
+ klass.fields.each do |f|
508
+ writeByte(f.type)
509
+ writeString(f.name)
510
+ writeObject(f.subtype) if f.subtype
511
+ end
512
+
513
+ writeByte(TC_ENDBLOCKDATA) # Annotations
514
+ writeObject(klass.superClass)
515
+ end
516
+
517
+ # Write the object and class to the stream.
518
+ def writeObjectData(klass, obj)
519
+ writeObjectData(klass.superClass, obj) if klass.superClass
520
+
521
+ if klass.flags == SC_SERIALIZABLE
522
+ klass.fields.each do |f|
523
+ v = obj.send(f.name.intern)
524
+ writeType(f.type, v)
525
+ end
526
+ else
527
+ writeBlockData(obj.data)
528
+ end
529
+ end
447
530
 
448
- def writeObject(obj)
531
+ # Writes the object and class data to the stream. Will
532
+ # write a reference if the object has already been written
533
+ # once.
534
+ def writeObject(obj)
449
535
  unless obj
450
536
  writeByte(TC_NULL)
451
537
  else
@@ -465,38 +551,39 @@ module Java
465
551
  writeObject(obj.class.javaClass)
466
552
  writeArray(obj.class.javaClass, obj)
467
553
  @handles[obj] = nextHandle
468
-
554
+
469
555
  when String
470
556
  writeByte(TC_STRING)
471
557
  writeString(obj)
472
558
  @handles[obj] = nextHandle
473
-
559
+
474
560
  else
475
561
  writeByte(TC_OBJECT)
476
562
  klass = obj.class.javaClass
477
563
  writeObject(klass)
478
564
  @handles[obj] = nextHandle
479
565
  writeObjectData(klass, obj)
480
-
481
566
  end
482
567
  end
483
568
  end
484
- end
569
+ end
485
570
 
486
- def writeObjects(objs)
487
- objs.each do |o|
488
- writeObject o
489
- end
490
- end
491
-
492
- def initialize(str)
493
- @str = str
494
- @handles = {}
495
- @nextHandle = 0
496
-
497
- writeUShort(STREAM_MAGIC)
498
- writeShort(STREAM_VERSION)
499
- end
571
+ # Write an array of objects to the stream.
572
+ def writeObjects(objs)
573
+ objs.each do |o|
574
+ writeObject o
575
+ end
576
+ end
577
+
578
+ # Create an o writer on with a stream.
579
+ def initialize(str)
580
+ @str = str
581
+ @handles = {}
582
+ @nextHandle = 0
583
+
584
+ writeUShort(STREAM_MAGIC)
585
+ writeShort(STREAM_VERSION)
586
+ end
500
587
  end
501
588
  end
502
589
 
data/rakefile CHANGED
@@ -6,9 +6,9 @@ require 'rake/packagetask'
6
6
  require 'rake/gempackagetask'
7
7
  require 'rake/contrib/rubyforgepublisher'
8
8
 
9
- PKG_BUILD = '1'
9
+ PKG_BUILD = '2'
10
10
  PKG_NAME = 'javaobj'
11
- PKG_VERSION = '0.1'
11
+ PKG_VERSION = '0.2'
12
12
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
13
13
 
14
14
  RELEASE_NAME = "REL #{PKG_VERSION}"
@@ -27,9 +27,10 @@ end
27
27
  Rake::RDocTask.new do |rdoc|
28
28
  rdoc.rdoc_dir = 'doc'
29
29
  rdoc.title = "Java Objects"
30
+ rdoc.main = 'README.rdoc'
30
31
  rdoc.options << '--line-numbers' << '--inline-source'
31
32
  rdoc.template = "#{ENV['template']}.rb" if ENV['template']
32
- rdoc.rdoc_files.include('lib/**/*.rb')
33
+ rdoc.rdoc_files.include('README.rdoc', 'lib/**/*.rb')
33
34
  end
34
35
 
35
36
  dist_dirs = [ "lib", "test", "examples" ]
metadata CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: javaobj
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.1"
6
+ version: "0.2"
7
7
  date: 2006-04-16 00:00:00 -07:00
8
8
  summary: Decode Java Serialized Objects to Ruby Objects.
9
9
  require_paths: