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.
- data/lib/javaobs.rb +200 -113
- data/rakefile +4 -3
- metadata +1 -1
data/lib/javaobs.rb
CHANGED
@@ -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
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
-
|
380
|
-
|
381
|
-
|
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
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
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
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
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
|
-
|
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
|
-
|
569
|
+
end
|
485
570
|
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
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 = '
|
9
|
+
PKG_BUILD = '2'
|
10
10
|
PKG_NAME = 'javaobj'
|
11
|
-
PKG_VERSION = '0.
|
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" ]
|