rocketamf_pure 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +9 -0
  3. data/benchmark.rb +73 -0
  4. data/lib/rocketamf.rb +212 -0
  5. data/lib/rocketamf/class_mapping.rb +237 -0
  6. data/lib/rocketamf/constants.rb +46 -0
  7. data/lib/rocketamf/ext.rb +28 -0
  8. data/lib/rocketamf/extensions.rb +22 -0
  9. data/lib/rocketamf/pure.rb +24 -0
  10. data/lib/rocketamf/pure/deserializer.rb +417 -0
  11. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  12. data/lib/rocketamf/pure/remoting.rb +117 -0
  13. data/lib/rocketamf/pure/serializer.rb +474 -0
  14. data/lib/rocketamf/remoting.rb +196 -0
  15. data/lib/rocketamf/values/messages.rb +212 -0
  16. data/lib/rocketamf/values/typed_hash.rb +13 -0
  17. data/lib/rocketamf_pure.rb +1 -0
  18. data/spec/class_mapping_spec.rb +110 -0
  19. data/spec/deserializer_spec.rb +423 -0
  20. data/spec/fast_class_mapping_spec.rb +144 -0
  21. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  22. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  23. data/spec/fixtures/objects/amf0-date.bin +0 -0
  24. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  25. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  26. data/spec/fixtures/objects/amf0-null.bin +1 -0
  27. data/spec/fixtures/objects/amf0-number.bin +0 -0
  28. data/spec/fixtures/objects/amf0-object.bin +0 -0
  29. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  30. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  31. data/spec/fixtures/objects/amf0-string.bin +0 -0
  32. data/spec/fixtures/objects/amf0-time.bin +0 -0
  33. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  34. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  35. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  36. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  37. data/spec/fixtures/objects/amf3-0.bin +0 -0
  38. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  39. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  40. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  41. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  42. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  43. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  44. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  45. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  46. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  47. data/spec/fixtures/objects/amf3-date.bin +0 -0
  48. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  49. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  50. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  51. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  52. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  53. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  54. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  55. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  56. data/spec/fixtures/objects/amf3-false.bin +1 -0
  57. data/spec/fixtures/objects/amf3-float.bin +0 -0
  58. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  59. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  60. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  61. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  62. data/spec/fixtures/objects/amf3-max.bin +1 -0
  63. data/spec/fixtures/objects/amf3-min.bin +0 -0
  64. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  65. data/spec/fixtures/objects/amf3-null.bin +1 -0
  66. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  67. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  68. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  69. data/spec/fixtures/objects/amf3-string.bin +1 -0
  70. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  71. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  72. data/spec/fixtures/objects/amf3-true.bin +1 -0
  73. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  74. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  75. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  76. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  77. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  78. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  79. data/spec/fixtures/request/blaze-response.bin +0 -0
  80. data/spec/fixtures/request/commandMessage.bin +0 -0
  81. data/spec/fixtures/request/flex-request.bin +0 -0
  82. data/spec/fixtures/request/multiple-simple-request.bin +0 -0
  83. data/spec/fixtures/request/remotingMessage.bin +0 -0
  84. data/spec/fixtures/request/simple-request.bin +0 -0
  85. data/spec/fixtures/request/simple-response.bin +0 -0
  86. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  87. data/spec/messages_spec.rb +39 -0
  88. data/spec/remoting_spec.rb +196 -0
  89. data/spec/serializer_spec.rb +503 -0
  90. data/spec/spec_helper.rb +55 -0
  91. metadata +148 -0
data/README.rdoc ADDED
@@ -0,0 +1,47 @@
1
+ == DESCRIPTION:
2
+
3
+ RocketAMF is a full featured AMF0/3 serializer and deserializer with support for
4
+ bi-directional Flash to Ruby class mapping, custom serialization and mapping,
5
+ remoting gateway helpers that follow AMF0/3 messaging specs, and a suite of specs
6
+ to ensure adherence to the specification documents put out by Adobe. If the C
7
+ components compile, then RocketAMF automatically takes advantage of them to
8
+ provide a substantial performance benefit. In addition, RocketAMF is fully
9
+ compatible with Ruby 1.9.
10
+
11
+ == INSTALL:
12
+
13
+ gem install RocketAMF
14
+
15
+ == SIMPLE EXAMPLE:
16
+
17
+ require 'rocketamf'
18
+
19
+ hash = {:apple => "Apfel", :red => "Rot", :eyes => "Augen"}
20
+ File.open("amf.dat", 'w') do |f|
21
+ f.write RocketAMF.serialize(hash, 3) # Use AMF3 encoding to serialize
22
+ end
23
+
24
+ == LICENSE:
25
+
26
+ (The MIT License)
27
+
28
+ Copyright (c) 2011 Stephen Augenstein and Jacob Henry
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining
31
+ a copy of this software and associated documentation files (the
32
+ 'Software'), to deal in the Software without restriction, including
33
+ without limitation the rights to use, copy, modify, merge, publish,
34
+ distribute, sublicense, and/or sell copies of the Software, and to
35
+ permit persons to whom the Software is furnished to do so, subject to
36
+ the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be
39
+ included in all copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
42
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
44
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
45
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
46
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
47
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Run all specs in the spec directory'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
data/benchmark.rb ADDED
@@ -0,0 +1,73 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/lib')
2
+ require 'rubygems'
3
+ require 'rocketamf'
4
+ require 'rocketamf/pure/deserializer' # Only ext gets included by default if available
5
+ require 'rocketamf/pure/serializer'
6
+
7
+ OBJECT_COUNT = 100000
8
+ TESTS = 5
9
+
10
+ class TestClass
11
+ attr_accessor :prop_a, :prop_b, :prop_c, :prop_d, :prop_e
12
+
13
+ def populate some_arg=nil # Make sure class mapper doesn't think populate is a property
14
+ @@count ||= 1
15
+ @prop_a = "asdfasdf #{@@count}"
16
+ @prop_b = "simple string"
17
+ @prop_c = 3120094.03
18
+ @prop_d = Time.now
19
+ @prop_e = 3120094
20
+ @@count += 1
21
+ self
22
+ end
23
+ end
24
+
25
+ objs = []
26
+ OBJECT_COUNT.times do
27
+ objs << TestClass.new.populate
28
+ end
29
+
30
+ ["native", "pure"].each do |type|
31
+ # Set up class mapper
32
+ cm = if type == "pure"
33
+ RocketAMF::ClassMapping
34
+ else
35
+ RocketAMF::Ext::FastClassMapping
36
+ end
37
+ cm.define do |m|
38
+ m.map :as => 'TestClass', :ruby => 'TestClass'
39
+ end
40
+
41
+ [0, 3].each do |version|
42
+ # 2**24 is larger than anyone is ever going to run this for
43
+ min_serialize = 2**24
44
+ min_deserialize = 2**24
45
+
46
+ puts "Testing #{type} AMF#{version}:"
47
+ TESTS.times do
48
+ ser = if type == "pure"
49
+ RocketAMF::Pure::Serializer.new(cm.new)
50
+ else
51
+ RocketAMF::Ext::Serializer.new(cm.new)
52
+ end
53
+ start_time = Time.now
54
+ out = ser.serialize(version, objs)
55
+ end_time = Time.now
56
+ puts "\tserialize run: #{end_time-start_time}s"
57
+ min_serialize = [end_time-start_time, min_serialize].min
58
+
59
+ des = if type == "pure"
60
+ RocketAMF::Pure::Deserializer.new(cm.new)
61
+ else
62
+ RocketAMF::Ext::Deserializer.new(cm.new)
63
+ end
64
+ start_time = Time.now
65
+ temp = des.deserialize(version, out)
66
+ end_time = Time.now
67
+ puts "\tdeserialize run: #{end_time-start_time}s"
68
+ min_deserialize = [end_time-start_time, min_deserialize].min
69
+ end
70
+ puts "\tminimum serialize time: #{min_serialize}s"
71
+ puts "\tminimum deserialize time: #{min_deserialize}s"
72
+ end
73
+ end
data/lib/rocketamf.rb ADDED
@@ -0,0 +1,212 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+ $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/rocketamf/"
3
+
4
+ require "date"
5
+ require "stringio"
6
+ require 'rocketamf/extensions'
7
+ require 'rocketamf/class_mapping'
8
+ require 'rocketamf/constants'
9
+ require 'rocketamf/remoting'
10
+
11
+ # RocketAMF is a full featured AMF0/3 serializer and deserializer with support for
12
+ # bi-directional Flash to Ruby class mapping, custom serialization and mapping,
13
+ # remoting gateway helpers that follow AMF0/3 messaging specs, and a suite of specs
14
+ # to ensure adherence to the specification documents put out by Adobe. If the C
15
+ # components compile, then RocketAMF automatically takes advantage of them to
16
+ # provide a substantial performance benefit. In addition, RocketAMF is fully
17
+ # compatible with Ruby 1.9.
18
+ #
19
+ # == Performance
20
+ #
21
+ # RocketAMF provides native C extensions for serialization, deserialization,
22
+ # remoting, and class mapping. If your environment supports them, RocketAMF will
23
+ # automatically take advantage of the C serializer, deserializer, and remoting
24
+ # support. The C class mapper has some substantial performance optimizations that
25
+ # make it incompatible with the pure Ruby class mapper, and so it must be manually
26
+ # enabled. For more information see <tt>RocketAMF::ClassMapping</tt>. Below are
27
+ # some benchmarks I took using using a simple little benchmarking utility I whipped
28
+ # up, which can be found in the root of the repository.
29
+ #
30
+ # # 100000 objects
31
+ # # Ruby 1.8
32
+ # Testing native AMF0:
33
+ # minimum serialize time: 1.229868s
34
+ # minimum deserialize time: 0.86465s
35
+ # Testing native AMF3:
36
+ # minimum serialize time: 1.444652s
37
+ # minimum deserialize time: 0.879407s
38
+ # Testing pure AMF0:
39
+ # minimum serialize time: 25.427931s
40
+ # minimum deserialize time: 11.706084s
41
+ # Testing pure AMF3:
42
+ # minimum serialize time: 31.637864s
43
+ # minimum deserialize time: 14.773969s
44
+ #
45
+ # == Serialization & Deserialization
46
+ #
47
+ # RocketAMF provides two main methods - <tt>serialize</tt> and <tt>deserialize</tt>.
48
+ # Deserialization takes a String or StringIO object and the AMF version if different
49
+ # from the default. Serialization takes any Ruby object and the version if different
50
+ # from the default. Both default to AMF0, as it's more widely supported and slightly
51
+ # faster, but AMF3 does a better job of not sending duplicate data. Which you choose
52
+ # depends on what you need to communicate with and how much serialized size matters.
53
+ #
54
+ # == Mapping Classes Between Flash and Ruby
55
+ #
56
+ # RocketAMF provides a simple class mapping tool to facilitate serialization and
57
+ # deserialization of typed objects. Refer to the documentation of
58
+ # <tt>RocketAMF::ClassMapping</tt> for more details. If the provided class
59
+ # mapping tool is not sufficient for your needs, you also have the option to
60
+ # replace it with a class mapper of your own devising that matches the documented
61
+ # API.
62
+ #
63
+ # == Remoting
64
+ #
65
+ # You can use RocketAMF bare to write an AMF gateway using the following code.
66
+ # In addition, you can use rack-amf (http://github.com/rubyamf/rack-amf) or
67
+ # RubyAMF (http://github.com/rubyamf/rubyamf), both of which provide rack-compliant
68
+ # AMF gateways.
69
+ #
70
+ # # helloworld.ru
71
+ # require 'rocketamf'
72
+ #
73
+ # class HelloWorldApp
74
+ # APPLICATION_AMF = 'application/x-amf'.freeze
75
+ #
76
+ # def call env
77
+ # if is_amf?(env)
78
+ # # Wrap request and response
79
+ # env['rack.input'].rewind
80
+ # request = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
81
+ # response = RocketAMF::Envelope.new
82
+ #
83
+ # # Handle request
84
+ # response.each_method_call request do |method, args|
85
+ # raise "Service #{method} does not exists" unless method == 'App.helloWorld'
86
+ # 'Hello world'
87
+ # end
88
+ #
89
+ # # Pass back response
90
+ # response_str = response.serialize
91
+ # return [200, {'Content-Type' => APPLICATION_AMF, 'Content-Length' => response_str.length.to_s}, [response_str]]
92
+ # else
93
+ # return [200, {'Content-Type' => 'text/plain', 'Content-Length' => '16' }, ["Rack AMF gateway"]]
94
+ # end
95
+ # end
96
+ #
97
+ # private
98
+ # def is_amf? env
99
+ # return false unless env['CONTENT_TYPE'] == APPLICATION_AMF
100
+ # return false unless env['PATH_INFO'] == '/amf'
101
+ # return true
102
+ # end
103
+ # end
104
+ #
105
+ # run HelloWorldApp.new
106
+ #
107
+ # == Advanced Serialization (encode_amf and IExternalizable)
108
+ #
109
+ # RocketAMF provides some additional functionality to support advanced
110
+ # serialization techniques. If you define an <tt>encode_amf</tt> method on your
111
+ # object, it will get called during serialization. It is passed a single argument,
112
+ # the serializer, and it can use the serializer stream, the <tt>serialize</tt>
113
+ # method, the <tt>write_array</tt> method, the <tt>write_object</tt> method, and
114
+ # the serializer version. Below is a simple example that uses <tt>write_object</tt>
115
+ # to customize the property hash that is used for serialization.
116
+ #
117
+ # Example:
118
+ #
119
+ # class TestObject
120
+ # def encode_amf ser
121
+ # ser.write_object self, @attributes
122
+ # end
123
+ # end
124
+ #
125
+ # If you plan on using the <tt>serialize</tt> method, make sure to pass in the
126
+ # current serializer version, or you could create a message that cannot be deserialized.
127
+ #
128
+ # Example:
129
+ #
130
+ # class VariableObject
131
+ # def encode_amf ser
132
+ # if ser.version == 0
133
+ # ser.serialize 0, true
134
+ # else
135
+ # ser.serialize 3, false
136
+ # end
137
+ # end
138
+ # end
139
+ #
140
+ # If you wish to send and receive IExternalizable objects, you will need to
141
+ # implement <tt>encode_amf</tt>, <tt>read_external</tt>, and <tt>write_external</tt>.
142
+ # Below is an example of a ResultSet class that extends Array and serializes as
143
+ # an array collection. RocketAMF can automatically serialize arrays as
144
+ # ArrayCollection objects, so this is just an example of how you might implement
145
+ # an object that conforms to IExternalizable.
146
+ #
147
+ # Example:
148
+ #
149
+ # class ResultSet < Array
150
+ # def encode_amf ser
151
+ # if ser.version == 0
152
+ # # Serialize as simple array in AMF0
153
+ # ser.write_array self
154
+ # else
155
+ # # Serialize as an ArrayCollection object
156
+ # # It conforms to IExternalizable, does not have any dynamic properties,
157
+ # # and has no "sealed" members. See the AMF3 specs for more details about
158
+ # # object traits.
159
+ # ser.write_object self, nil, {
160
+ # :class_name => "flex.messaging.io.ArrayCollection",
161
+ # :externalizable => true,
162
+ # :dynamic => false,
163
+ # :members => []
164
+ # }
165
+ # end
166
+ # end
167
+ #
168
+ # # Write self as array to stream
169
+ # def write_external ser
170
+ # ser.write_array(self)
171
+ # end
172
+ #
173
+ # # Read array out and replace data with deserialized array.
174
+ # def read_external des
175
+ # replace(des.read_object)
176
+ # end
177
+ # end
178
+ module RocketAMF
179
+ require 'rocketamf/pure'
180
+
181
+ # Deserialize the AMF string _source_ of the given AMF version into a Ruby
182
+ # data structure and return it. Creates an instance of <tt>RocketAMF::Deserializer</tt>
183
+ # with a new instance of <tt>RocketAMF::ClassMapper</tt> and calls deserialize
184
+ # on it with the given source and amf version, returning the result.
185
+ def self.deserialize source, amf_version = 0
186
+ des = RocketAMF::Deserializer.new(RocketAMF::ClassMapper.new)
187
+ des.deserialize(amf_version, source)
188
+ end
189
+
190
+ # Serialize the given Ruby data structure _obj_ into an AMF stream using the
191
+ # given AMF version. Creates an instance of <tt>RocketAMF::Serializer</tt>
192
+ # with a new instance of <tt>RocketAMF::ClassMapper</tt> and calls serialize
193
+ # on it with the given object and amf version, returning the result.
194
+ def self.serialize obj, amf_version = 0
195
+ ser = RocketAMF::Serializer.new(RocketAMF::ClassMapper.new)
196
+ ser.serialize(amf_version, obj)
197
+ end
198
+
199
+ # We use const_missing to define the active ClassMapper at runtime. This way,
200
+ # heavy modification of class mapping functionality is still possible without
201
+ # forcing extenders to redefine the constant.
202
+ def self.const_missing const #:nodoc:
203
+ if const == :ClassMapper
204
+ RocketAMF.const_set(:ClassMapper, RocketAMF::ClassMapping)
205
+ else
206
+ super(const)
207
+ end
208
+ end
209
+
210
+ # The base exception for AMF errors.
211
+ class AMFError < StandardError; end
212
+ end
@@ -0,0 +1,237 @@
1
+ require 'rocketamf/values/typed_hash'
2
+ require 'rocketamf/values/messages'
3
+
4
+ module RocketAMF
5
+ # Container for all mapped classes
6
+ class MappingSet
7
+ # Creates a mapping set object and populates the default mappings
8
+ def initialize
9
+ @as_mappings = {}
10
+ @ruby_mappings = {}
11
+ map_defaults
12
+ end
13
+
14
+ # Adds required mapping configs, calling map for the required base mappings.
15
+ # Designed to allow extenders to take advantage of required default mappings.
16
+ def map_defaults
17
+ map :as => 'flex.messaging.messages.AbstractMessage', :ruby => 'RocketAMF::Values::AbstractMessage'
18
+ map :as => 'flex.messaging.messages.RemotingMessage', :ruby => 'RocketAMF::Values::RemotingMessage'
19
+ map :as => 'flex.messaging.messages.AsyncMessage', :ruby => 'RocketAMF::Values::AsyncMessage'
20
+ map :as => 'DSA', :ruby => 'RocketAMF::Values::AsyncMessageExt'
21
+ map :as => 'flex.messaging.messages.CommandMessage', :ruby => 'RocketAMF::Values::CommandMessage'
22
+ map :as => 'DSC', :ruby => 'RocketAMF::Values::CommandMessageExt'
23
+ map :as => 'flex.messaging.messages.AcknowledgeMessage', :ruby => 'RocketAMF::Values::AcknowledgeMessage'
24
+ map :as => 'DSK', :ruby => 'RocketAMF::Values::AcknowledgeMessageExt'
25
+ map :as => 'flex.messaging.messages.ErrorMessage', :ruby => 'RocketAMF::Values::ErrorMessage'
26
+ self
27
+ end
28
+
29
+ # Map a given AS class to a ruby class.
30
+ #
31
+ # Use fully qualified names for both.
32
+ #
33
+ # Example:
34
+ #
35
+ # m.map :as => 'com.example.Date', :ruby => 'Example::Date'
36
+ def map params
37
+ [:as, :ruby].each {|k| params[k] = params[k].to_s} # Convert params to strings
38
+ @as_mappings[params[:as]] = params[:ruby]
39
+ @ruby_mappings[params[:ruby]] = params[:as]
40
+ end
41
+
42
+ # Returns the AS class name for the given ruby class name, returing nil if
43
+ # not found
44
+ def get_as_class_name class_name #:nodoc:
45
+ @ruby_mappings[class_name.to_s]
46
+ end
47
+
48
+ # Returns the ruby class name for the given AS class name, returing nil if
49
+ # not found
50
+ def get_ruby_class_name class_name #:nodoc:
51
+ @as_mappings[class_name.to_s]
52
+ end
53
+ end
54
+
55
+ # Handles class name mapping between actionscript and ruby and assists in
56
+ # serializing and deserializing data between them. Simply map an AS class to a
57
+ # ruby class and when the object is (de)serialized it will end up as the
58
+ # appropriate class.
59
+ #
60
+ # Example:
61
+ #
62
+ # RocketAMF::ClassMapper.define do |m|
63
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
64
+ # m.map :as => 'vo.User', :ruby => 'Model::User'
65
+ # end
66
+ #
67
+ # == Object Population/Serialization
68
+ #
69
+ # In addition to handling class name mapping, it also provides helper methods
70
+ # for populating ruby objects from AMF and extracting properties from ruby objects
71
+ # for serialization. Support for hash-like objects and objects using
72
+ # <tt>attr_accessor</tt> for properties is currently built in, but custom classes
73
+ # may require subclassing the class mapper to add support.
74
+ #
75
+ # == Complete Replacement
76
+ #
77
+ # In some cases, it may be beneficial to replace the default provider of class
78
+ # mapping completely. In this case, simply assign your class mapper class to
79
+ # <tt>RocketAMF::ClassMapper</tt> after loading RocketAMF. Through the magic of
80
+ # <tt>const_missing</tt>, <tt>ClassMapper</tt> is only defined after the first
81
+ # access by default, so you get no annoying warning messages. Custom class mappers
82
+ # must implement the following methods on instances: <tt>use_array_collection</tt>,
83
+ # <tt>get_as_class_name</tt>, <tt>get_ruby_obj</tt>, <tt>populate_ruby_obj</tt>,
84
+ # and <tt>props_for_serialization</tt>. In addition, it should have a class level
85
+ # <tt>mappings</tt> method that returns the mapping set it's using, although its
86
+ # not required. If you'd like to see an example of what complete replacement
87
+ # offers, check out RubyAMF (http://github.com/rubyamf/rubyamf).
88
+ #
89
+ # Example:
90
+ #
91
+ # require 'rubygems'
92
+ # require 'rocketamf'
93
+ #
94
+ # RocketAMF::ClassMapper = MyCustomClassMapper
95
+ # # No warning about already initialized constant ClassMapper
96
+ # RocketAMF::ClassMapper # MyCustomClassMapper
97
+ #
98
+ # == C ClassMapper
99
+ #
100
+ # The C class mapper, <tt>RocketAMF::Ext::FastClassMapping</tt>, has the same
101
+ # public API that <tt>RubyAMF::ClassMapping</tt> does, but has some additional
102
+ # performance optimizations that may interfere with the proper serialization of
103
+ # objects. To reduce the cost of processing public methods for every object,
104
+ # its implementation of <tt>props_for_serialization</tt> caches valid properties
105
+ # by class, using the class as the hash key for property lookup. This means that
106
+ # adding and removing properties from instances while serializing using a given
107
+ # class mapper instance will result in the changes not being detected. As such,
108
+ # it's not enabled by default. So long as you aren't planning on modifying
109
+ # classes during serialization using <tt>encode_amf</tt>, the faster C class
110
+ # mapper should be perfectly safe to use.
111
+ #
112
+ # Activating the C Class Mapper:
113
+ #
114
+ # require 'rubygems'
115
+ # require 'rocketamf'
116
+ # RocketAMF::ClassMapper = RocketAMF::Ext::FastClassMapping
117
+ class ClassMapping
118
+ class << self
119
+ # Global configuration variable for sending Arrays as ArrayCollections.
120
+ # Defaults to false.
121
+ attr_accessor :use_array_collection
122
+
123
+ # Returns the mapping set with all the class mappings that is currently
124
+ # being used.
125
+ def mappings
126
+ @mappings ||= MappingSet.new
127
+ end
128
+
129
+ # Define class mappings in the block. Block is passed a <tt>MappingSet</tt> object
130
+ # as the first parameter.
131
+ #
132
+ # Example:
133
+ #
134
+ # RocketAMF::ClassMapper.define do |m|
135
+ # m.map :as => 'AsClass', :ruby => 'RubyClass'
136
+ # end
137
+ def define &block #:yields: mapping_set
138
+ yield mappings
139
+ end
140
+
141
+ # Reset all class mappings except the defaults and return
142
+ # <tt>use_array_collection</tt> to false
143
+ def reset
144
+ @use_array_collection = false
145
+ @mappings = nil
146
+ end
147
+ end
148
+
149
+ attr_reader :use_array_collection
150
+
151
+ # Copies configuration from class level configs to populate object
152
+ def initialize
153
+ @mappings = self.class.mappings
154
+ @use_array_collection = self.class.use_array_collection === true
155
+ end
156
+
157
+ # Returns the ActionScript class name for the given ruby object. Will also
158
+ # take a string containing the ruby class name.
159
+ def get_as_class_name obj
160
+ # Get class name
161
+ if obj.is_a?(String)
162
+ ruby_class_name = obj
163
+ elsif obj.is_a?(Values::TypedHash)
164
+ ruby_class_name = obj.type
165
+ elsif obj.is_a?(Hash)
166
+ return nil
167
+ else
168
+ ruby_class_name = obj.class.name
169
+ end
170
+
171
+ # Get mapped AS class name
172
+ @mappings.get_as_class_name ruby_class_name
173
+ end
174
+
175
+ # Instantiates a ruby object using the mapping configuration based on the
176
+ # source ActionScript class name. If there is no mapping defined, it returns
177
+ # a <tt>RocketAMF::Values::TypedHash</tt> with the serialized class name.
178
+ def get_ruby_obj as_class_name
179
+ ruby_class_name = @mappings.get_ruby_class_name as_class_name
180
+ if ruby_class_name.nil?
181
+ # Populate a simple hash, since no mapping
182
+ return Values::TypedHash.new(as_class_name)
183
+ else
184
+ ruby_class = ruby_class_name.split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
185
+ return ruby_class.new
186
+ end
187
+ end
188
+
189
+ # Populates the ruby object using the given properties. props and
190
+ # dynamic_props will be hashes with symbols for keys.
191
+ def populate_ruby_obj obj, props, dynamic_props=nil
192
+ props.merge! dynamic_props if dynamic_props
193
+
194
+ # Don't even bother checking if it responds to setter methods if it's a TypedHash
195
+ if obj.is_a?(Values::TypedHash)
196
+ obj.merge! props
197
+ return obj
198
+ end
199
+
200
+ # Some type of object
201
+ hash_like = obj.respond_to?("[]=")
202
+ props.each do |key, value|
203
+ if obj.respond_to?("#{key}=")
204
+ obj.send("#{key}=", value)
205
+ elsif hash_like
206
+ obj[key] = value
207
+ end
208
+ end
209
+ obj
210
+ end
211
+
212
+ # Extracts all exportable properties from the given ruby object and returns
213
+ # them in a hash. If overriding, make sure to return a hash wth string keys
214
+ # unless you are only going to be using the native C extensions, as the pure
215
+ # ruby serializer performs a sort on the keys to acheive consistent, testable
216
+ # results.
217
+ def props_for_serialization ruby_obj
218
+ # Handle hashes
219
+ if ruby_obj.is_a?(Hash)
220
+ # Stringify keys to make it easier later on and allow sorting
221
+ h = {}
222
+ ruby_obj.each {|k,v| h[k.to_s] = v}
223
+ return h
224
+ end
225
+
226
+ # Generic object serializer
227
+ props = {}
228
+ @ignored_props ||= Object.new.public_methods
229
+ (ruby_obj.public_methods - @ignored_props).each do |method_name|
230
+ # Add them to the prop hash if they take no arguments
231
+ method_def = ruby_obj.method(method_name)
232
+ props[method_name.to_s] = ruby_obj.send(method_name) if method_def.arity == 0
233
+ end
234
+ props
235
+ end
236
+ end
237
+ end