rocketamf_pure 1.0.0

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 (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