javaobj 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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" ]
|