scalm-RocketAMF 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 (106) hide show
  1. data/README.rdoc +47 -0
  2. data/Rakefile +59 -0
  3. data/RocketAMF.gemspec +20 -0
  4. data/benchmark.rb +74 -0
  5. data/ext/rocketamf_ext/class_mapping.c +484 -0
  6. data/ext/rocketamf_ext/constants.h +52 -0
  7. data/ext/rocketamf_ext/deserializer.c +776 -0
  8. data/ext/rocketamf_ext/deserializer.h +28 -0
  9. data/ext/rocketamf_ext/extconf.rb +18 -0
  10. data/ext/rocketamf_ext/remoting.c +184 -0
  11. data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
  12. data/ext/rocketamf_ext/serializer.c +834 -0
  13. data/ext/rocketamf_ext/serializer.h +29 -0
  14. data/ext/rocketamf_ext/utility.h +4 -0
  15. data/lib/rocketamf.rb +216 -0
  16. data/lib/rocketamf/class_mapping.rb +237 -0
  17. data/lib/rocketamf/constants.rb +50 -0
  18. data/lib/rocketamf/ext.rb +28 -0
  19. data/lib/rocketamf/extensions.rb +22 -0
  20. data/lib/rocketamf/pure.rb +24 -0
  21. data/lib/rocketamf/pure/deserializer.rb +455 -0
  22. data/lib/rocketamf/pure/io_helpers.rb +94 -0
  23. data/lib/rocketamf/pure/remoting.rb +117 -0
  24. data/lib/rocketamf/pure/serializer.rb +474 -0
  25. data/lib/rocketamf/remoting.rb +196 -0
  26. data/lib/rocketamf/values/messages.rb +214 -0
  27. data/lib/rocketamf/values/typed_hash.rb +13 -0
  28. data/spec/class_mapping_spec.rb +110 -0
  29. data/spec/deserializer_spec.rb +455 -0
  30. data/spec/fast_class_mapping_spec.rb +144 -0
  31. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  32. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  33. data/spec/fixtures/objects/amf0-date.bin +0 -0
  34. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  35. data/spec/fixtures/objects/amf0-empty-string-key-hash.bin +0 -0
  36. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  37. data/spec/fixtures/objects/amf0-null.bin +1 -0
  38. data/spec/fixtures/objects/amf0-number.bin +0 -0
  39. data/spec/fixtures/objects/amf0-object.bin +0 -0
  40. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  41. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  42. data/spec/fixtures/objects/amf0-string.bin +0 -0
  43. data/spec/fixtures/objects/amf0-time.bin +0 -0
  44. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  45. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  46. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  47. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  48. data/spec/fixtures/objects/amf3-0.bin +0 -0
  49. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  50. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  51. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  52. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  53. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  54. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  55. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  56. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  57. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  58. data/spec/fixtures/objects/amf3-date.bin +0 -0
  59. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  60. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  61. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  62. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  63. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  64. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  65. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  66. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  67. data/spec/fixtures/objects/amf3-false.bin +1 -0
  68. data/spec/fixtures/objects/amf3-float.bin +0 -0
  69. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  70. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  71. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  72. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  73. data/spec/fixtures/objects/amf3-max.bin +1 -0
  74. data/spec/fixtures/objects/amf3-min.bin +0 -0
  75. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  76. data/spec/fixtures/objects/amf3-null.bin +1 -0
  77. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  78. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  79. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  80. data/spec/fixtures/objects/amf3-string.bin +1 -0
  81. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  82. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  83. data/spec/fixtures/objects/amf3-true.bin +1 -0
  84. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  85. data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
  86. data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
  87. data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
  88. data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
  89. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  90. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  91. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  92. data/spec/fixtures/request/acknowledge-response.bin +0 -0
  93. data/spec/fixtures/request/amf0-error-response.bin +0 -0
  94. data/spec/fixtures/request/blaze-response.bin +0 -0
  95. data/spec/fixtures/request/commandMessage.bin +0 -0
  96. data/spec/fixtures/request/flex-request.bin +0 -0
  97. data/spec/fixtures/request/multiple-simple-request.bin +0 -0
  98. data/spec/fixtures/request/remotingMessage.bin +0 -0
  99. data/spec/fixtures/request/simple-request.bin +0 -0
  100. data/spec/fixtures/request/simple-response.bin +0 -0
  101. data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
  102. data/spec/messages_spec.rb +39 -0
  103. data/spec/remoting_spec.rb +196 -0
  104. data/spec/serializer_spec.rb +503 -0
  105. data/spec/spec_helper.rb +55 -0
  106. metadata +164 -0
@@ -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.
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'rspec/core/rake_task'
6
+ require 'rake/extensiontask'
7
+
8
+ desc 'Default: run the specs.'
9
+ task :default => :spec
10
+
11
+ # I don't want to depend on bundler, so we do it the bundler way without it
12
+ gemspec_path = 'RocketAMF.gemspec'
13
+ spec = begin
14
+ eval(File.read(File.join(File.dirname(__FILE__), gemspec_path)), TOPLEVEL_BINDING, gemspec_path)
15
+ rescue LoadError => e
16
+ original_line = e.backtrace.find { |line| line.include?(gemspec_path) }
17
+ msg = "There was a LoadError while evaluating #{gemspec_path}:\n #{e.message}"
18
+ msg << " from\n #{original_line}" if original_line
19
+ msg << "\n"
20
+ puts msg
21
+ exit
22
+ end
23
+
24
+ RSpec::Core::RakeTask.new do |t|
25
+ end
26
+
27
+ desc 'Generate documentation'
28
+ Rake::RDocTask.new(:rdoc) do |rdoc|
29
+ rdoc.rdoc_dir = 'rdoc'
30
+ rdoc.title = spec.name
31
+ rdoc.options += spec.rdoc_options
32
+ rdoc.rdoc_files.include(*spec.extra_rdoc_files)
33
+ rdoc.rdoc_files.include("lib") # Don't include ext folder because no one cares
34
+ end
35
+
36
+ Rake::GemPackageTask.new(spec) do |pkg|
37
+ pkg.need_zip = false
38
+ pkg.need_tar = false
39
+ end
40
+
41
+ Rake::ExtensionTask.new('rocketamf_ext', spec) do |ext|
42
+ if RUBY_PLATFORM =~ /mswin|mingw/ then
43
+ # No cross-compile on win, so compile extension to lib/1.[89]
44
+ RUBY_VERSION =~ /(\d+\.\d+)/
45
+ ext.lib_dir = "lib/#{$1}"
46
+ else
47
+ ext.cross_compile = true
48
+ ext.cross_platform = 'x86-mingw32'
49
+ ext.cross_compiling do |gem_spec|
50
+ gem_spec.post_install_message = "You installed the binary version of this gem!"
51
+ end
52
+ end
53
+ #ext.config_options << '--enable-sort-props'
54
+ end
55
+
56
+ desc "Build gem packages"
57
+ task :gems do
58
+ sh "rake cross native gem RUBY_CC_VERSION=1.8.7:1.9.2"
59
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'scalm-RocketAMF'
5
+ s.version = '1.0.0'
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ['Jacob Henry', 'Stephen Augenstein', "Joc O'Connor"]
8
+ s.email = ['perl.programmer@gmail.com']
9
+ s.homepage = 'http://github.com/scalm/rocketamf'
10
+ s.summary = 'Fast AMF serializer/deserializer with remoting request/response wrappers to simplify integration'
11
+
12
+ s.files = Dir[*['README.rdoc', 'benchmark.rb', 'RocketAMF.gemspec', 'Rakefile', 'lib/**/*.rb', 'spec/**/*.{rb,bin,opts}', 'ext/**/*.{c,h,rb}']]
13
+ s.test_files = Dir[*['spec/**/*_spec.rb']]
14
+ s.extensions = Dir[*["ext/**/extconf.rb"]]
15
+ s.require_paths = ["lib"]
16
+
17
+ s.has_rdoc = true
18
+ s.extra_rdoc_files = ['README.rdoc']
19
+ s.rdoc_options = ['--line-numbers', '--main', 'README.rdoc']
20
+ end
@@ -0,0 +1,74 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/ext')
2
+ $:.unshift(File.dirname(__FILE__) + '/lib')
3
+ require 'rubygems'
4
+ require 'rocketamf'
5
+ require 'rocketamf/pure/deserializer' # Only ext gets included by default if available
6
+ require 'rocketamf/pure/serializer'
7
+
8
+ OBJECT_COUNT = 100000
9
+ TESTS = 5
10
+
11
+ class TestClass
12
+ attr_accessor :prop_a, :prop_b, :prop_c, :prop_d, :prop_e
13
+
14
+ def populate some_arg=nil # Make sure class mapper doesn't think populate is a property
15
+ @@count ||= 1
16
+ @prop_a = "asdfasdf #{@@count}"
17
+ @prop_b = "simple string"
18
+ @prop_c = 3120094.03
19
+ @prop_d = Time.now
20
+ @prop_e = 3120094
21
+ @@count += 1
22
+ self
23
+ end
24
+ end
25
+
26
+ objs = []
27
+ OBJECT_COUNT.times do
28
+ objs << TestClass.new.populate
29
+ end
30
+
31
+ ["native", "pure"].each do |type|
32
+ # Set up class mapper
33
+ cm = if type == "pure"
34
+ RocketAMF::ClassMapping
35
+ else
36
+ RocketAMF::Ext::FastClassMapping
37
+ end
38
+ cm.define do |m|
39
+ m.map :as => 'TestClass', :ruby => 'TestClass'
40
+ end
41
+
42
+ [0, 3].each do |version|
43
+ # 2**24 is larger than anyone is ever going to run this for
44
+ min_serialize = 2**24
45
+ min_deserialize = 2**24
46
+
47
+ puts "Testing #{type} AMF#{version}:"
48
+ TESTS.times do
49
+ ser = if type == "pure"
50
+ RocketAMF::Pure::Serializer.new(cm.new)
51
+ else
52
+ RocketAMF::Ext::Serializer.new(cm.new)
53
+ end
54
+ start_time = Time.now
55
+ out = ser.serialize(version, objs)
56
+ end_time = Time.now
57
+ puts "\tserialize run: #{end_time-start_time}s"
58
+ min_serialize = [end_time-start_time, min_serialize].min
59
+
60
+ des = if type == "pure"
61
+ RocketAMF::Pure::Deserializer.new(cm.new)
62
+ else
63
+ RocketAMF::Ext::Deserializer.new(cm.new)
64
+ end
65
+ start_time = Time.now
66
+ temp = des.deserialize(version, out)
67
+ end_time = Time.now
68
+ puts "\tdeserialize run: #{end_time-start_time}s"
69
+ min_deserialize = [end_time-start_time, min_deserialize].min
70
+ end
71
+ puts "\tminimum serialize time: #{min_serialize}s"
72
+ puts "\tminimum deserialize time: #{min_deserialize}s"
73
+ end
74
+ end
@@ -0,0 +1,484 @@
1
+ #include <ruby.h>
2
+ #ifdef HAVE_RB_STR_ENCODE
3
+ #include <ruby/st.h>
4
+ #else
5
+ #include <st.h>
6
+ #endif
7
+ #include "utility.h"
8
+
9
+ extern VALUE mRocketAMF;
10
+ extern VALUE mRocketAMFExt;
11
+ VALUE cFastMappingSet;
12
+ VALUE cTypedHash;
13
+ ID id_use_ac;
14
+ ID id_use_ac_ivar;
15
+ ID id_mappings;
16
+ ID id_mappings_ivar;
17
+ ID id_hashset;
18
+
19
+ typedef struct {
20
+ VALUE mapset;
21
+ st_table* setter_cache;
22
+ st_table* prop_cache;
23
+ } CLASS_MAPPING;
24
+
25
+ typedef struct {
26
+ st_table* as_mappings;
27
+ st_table* rb_mappings;
28
+ } MAPSET;
29
+
30
+ /*
31
+ * Mark the as_mappings and rb_mappings hashes
32
+ */
33
+ static void mapset_mark(MAPSET *set) {
34
+ if(!set) return;
35
+ rb_mark_tbl(set->as_mappings);
36
+ rb_mark_tbl(set->rb_mappings);
37
+ }
38
+
39
+ /*
40
+ * Free the mapping tables and struct
41
+ */
42
+ int mapset_free_strtable_key(st_data_t key, st_data_t value, st_data_t ignored) {
43
+ xfree((void *)key);
44
+ return ST_DELETE;
45
+ }
46
+ static void mapset_free(MAPSET *set) {
47
+ st_foreach(set->as_mappings, mapset_free_strtable_key, 0);
48
+ st_free_table(set->as_mappings);
49
+ set->as_mappings = NULL;
50
+ st_foreach(set->rb_mappings, mapset_free_strtable_key, 0);
51
+ st_free_table(set->rb_mappings);
52
+ set->rb_mappings = NULL;
53
+ xfree(set);
54
+ }
55
+
56
+ /*
57
+ * Allocate mapset and populate mappings with built-in mappings
58
+ */
59
+ static VALUE mapset_alloc(VALUE klass) {
60
+ MAPSET *set = ALLOC(MAPSET);
61
+ memset(set, 0, sizeof(MAPSET));
62
+ VALUE self = Data_Wrap_Struct(klass, mapset_mark, mapset_free, set);
63
+
64
+ // Initialize internal data
65
+ set->as_mappings = st_init_strtable();
66
+ set->rb_mappings = st_init_strtable();
67
+
68
+ return self;
69
+ }
70
+
71
+ /*
72
+ * call-seq:
73
+ * RocketAMF::Ext::MappingSet.new
74
+ *
75
+ * Creates a mapping set object and populates the default mappings
76
+ */
77
+ static VALUE mapset_init(VALUE self) {
78
+ rb_funcall(self, rb_intern("map_defaults"), 0);
79
+ return self;
80
+ }
81
+
82
+ /*
83
+ * call-seq:
84
+ * m.map_defaults
85
+ *
86
+ * Adds required mapping configs, calling map for the required base mappings
87
+ */
88
+ static VALUE mapset_map_defaults(VALUE self) {
89
+ const int NUM_MAPPINGS = 9;
90
+ const char* ruby_classes[] = {
91
+ "RocketAMF::Values::AbstractMessage",
92
+ "RocketAMF::Values::RemotingMessage",
93
+ "RocketAMF::Values::AsyncMessage",
94
+ "RocketAMF::Values::AsyncMessageExt",
95
+ "RocketAMF::Values::CommandMessage",
96
+ "RocketAMF::Values::CommandMessageExt",
97
+ "RocketAMF::Values::AcknowledgeMessage",
98
+ "RocketAMF::Values::AcknowledgeMessageExt",
99
+ "RocketAMF::Values::ErrorMessage"
100
+ };
101
+ const char* as_classes[] = {
102
+ "flex.messaging.messages.AbstractMessage",
103
+ "flex.messaging.messages.RemotingMessage",
104
+ "flex.messaging.messages.AsyncMessage",
105
+ "DSA",
106
+ "flex.messaging.messages.CommandMessage",
107
+ "DSC",
108
+ "flex.messaging.messages.AcknowledgeMessage",
109
+ "DSK",
110
+ "flex.messaging.messages.ErrorMessage"
111
+ };
112
+
113
+ int i;
114
+ ID map_id = rb_intern("map");
115
+ VALUE params = rb_hash_new();
116
+ VALUE as_sym = ID2SYM(rb_intern("as"));
117
+ VALUE ruby_sym = ID2SYM(rb_intern("ruby"));
118
+ for(i = 0; i < NUM_MAPPINGS; i++) {
119
+ rb_hash_aset(params, as_sym, rb_str_new2(as_classes[i]));
120
+ rb_hash_aset(params, ruby_sym, rb_str_new2(ruby_classes[i]));
121
+ rb_funcall(self, map_id, 1, params);
122
+ }
123
+
124
+ return self;
125
+ }
126
+
127
+ /*
128
+ * call-seq:
129
+ * m.map :as => 'com.example.Date', :ruby => "Example::Date'
130
+ *
131
+ * Map a given AS class to a ruby class. Use fully qualified names for both.
132
+ */
133
+ static VALUE mapset_map(VALUE self, VALUE mapping) {
134
+ MAPSET *set;
135
+ Data_Get_Struct(self, MAPSET, set);
136
+
137
+ VALUE as_class = rb_hash_aref(mapping, ID2SYM(rb_intern("as")));
138
+ VALUE rb_class = rb_hash_aref(mapping, ID2SYM(rb_intern("ruby")));
139
+ st_insert(set->as_mappings, (st_data_t)strdup(RSTRING_PTR(as_class)), rb_class);
140
+ st_insert(set->rb_mappings, (st_data_t)strdup(RSTRING_PTR(rb_class)), as_class);
141
+
142
+ return Qnil;
143
+ }
144
+
145
+ /*
146
+ * Internal method for looking up a given ruby class's AS class name or Qnil if
147
+ * not found
148
+ */
149
+ static VALUE mapset_as_lookup(VALUE self, const char* class_name) {
150
+ MAPSET *set;
151
+ Data_Get_Struct(self, MAPSET, set);
152
+
153
+ VALUE as_name;
154
+ if(st_lookup(set->rb_mappings, (st_data_t)class_name, &as_name)) {
155
+ return as_name;
156
+ } else {
157
+ return Qnil;
158
+ }
159
+ }
160
+
161
+ /*
162
+ * Internal method for looking up a given AS class names ruby class name mapping
163
+ * or Qnil if not found
164
+ */
165
+ static VALUE mapset_rb_lookup(VALUE self, const char* class_name) {
166
+ MAPSET *set;
167
+ Data_Get_Struct(self, MAPSET, set);
168
+
169
+ VALUE rb_name;
170
+ if(st_lookup(set->as_mappings, (st_data_t)class_name, &rb_name)) {
171
+ return rb_name;
172
+ } else {
173
+ return Qnil;
174
+ }
175
+ }
176
+
177
+ /*
178
+ * Mark the mapset object and property lookup cache
179
+ */
180
+ static void mapping_mark(CLASS_MAPPING *map) {
181
+ if(!map) return;
182
+ rb_gc_mark(map->mapset);
183
+ rb_mark_tbl(map->prop_cache);
184
+ }
185
+
186
+ /*
187
+ * Free prop cache table and struct
188
+ */
189
+ static void mapping_free(CLASS_MAPPING *map) {
190
+ st_free_table(map->setter_cache);
191
+ st_free_table(map->prop_cache);
192
+ xfree(map);
193
+ }
194
+
195
+ /*
196
+ * Allocate class mapping struct
197
+ */
198
+ static VALUE mapping_alloc(VALUE klass) {
199
+ CLASS_MAPPING *map = ALLOC(CLASS_MAPPING);
200
+ memset(map, 0, sizeof(CLASS_MAPPING));
201
+ VALUE self = Data_Wrap_Struct(klass, mapping_mark, mapping_free, map);
202
+ map->setter_cache = st_init_numtable();
203
+ map->prop_cache = st_init_numtable();
204
+ return self;
205
+ }
206
+
207
+ /*
208
+ * Class-level getter for use_array_collection
209
+ */
210
+ static VALUE mapping_s_array_collection_get(VALUE klass) {
211
+ VALUE use_ac = rb_ivar_get(klass, id_use_ac_ivar);
212
+ if(use_ac == Qnil) {
213
+ use_ac = Qfalse;
214
+ rb_ivar_set(klass, id_use_ac_ivar, use_ac);
215
+ }
216
+ return use_ac;
217
+ }
218
+
219
+ /*
220
+ * Class-level setter for use_array_collection
221
+ */
222
+ static VALUE mapping_s_array_collection_set(VALUE klass, VALUE use_ac) {
223
+ return rb_ivar_set(klass, id_use_ac_ivar, use_ac);
224
+ }
225
+
226
+ /*
227
+ * Return MappingSet for class mapper, creating if uninitialized
228
+ */
229
+ static VALUE mapping_s_mappings(VALUE klass) {
230
+ VALUE mappings = rb_ivar_get(klass, id_mappings_ivar);
231
+ if(mappings == Qnil) {
232
+ mappings = rb_class_new_instance(0, NULL, cFastMappingSet);
233
+ rb_ivar_set(klass, id_mappings_ivar, mappings);
234
+ }
235
+ return mappings;
236
+ }
237
+
238
+ /*
239
+ * call-seq:
240
+ * mapper.define {|m| block } => nil
241
+ *
242
+ * Define class mappings in the block. Block is passed a MappingSet object as
243
+ * the first parameter. See RocketAMF::ClassMapping for details.
244
+ */
245
+ static VALUE mapping_s_define(VALUE klass) {
246
+ if (rb_block_given_p()) {
247
+ VALUE mappings = rb_funcall(klass, id_mappings, 0);
248
+ rb_yield(mappings);
249
+ }
250
+ return Qnil;
251
+ }
252
+
253
+ /*
254
+ * Reset class mappings
255
+ */
256
+ static VALUE mapping_s_reset(VALUE klass) {
257
+ rb_ivar_set(klass, id_use_ac_ivar, Qfalse);
258
+ rb_ivar_set(klass, id_mappings_ivar, Qnil);
259
+ return Qnil;
260
+ }
261
+
262
+ /*
263
+ * Initialize class mapping object, setting use_class_mapping to false
264
+ */
265
+ static VALUE mapping_init(VALUE self) {
266
+ CLASS_MAPPING *map;
267
+ Data_Get_Struct(self, CLASS_MAPPING, map);
268
+ map->mapset = rb_funcall(CLASS_OF(self), id_mappings, 0);
269
+ VALUE use_ac = rb_funcall(CLASS_OF(self), id_use_ac, 0);
270
+ rb_ivar_set(self, id_use_ac_ivar, use_ac);
271
+ return self;
272
+ }
273
+
274
+ /*
275
+ * call-seq:
276
+ * mapper.get_as_class_name => str
277
+ *
278
+ * Returns the AS class name for the given ruby object. Will also take a string
279
+ * containing the ruby class name.
280
+ */
281
+ static VALUE mapping_as_class_name(VALUE self, VALUE obj) {
282
+ CLASS_MAPPING *map;
283
+ Data_Get_Struct(self, CLASS_MAPPING, map);
284
+
285
+ int type = TYPE(obj);
286
+ const char* class_name;
287
+ if(type == T_STRING) {
288
+ // Use strings as the class name
289
+ class_name = RSTRING_PTR(obj);
290
+ } else {
291
+ // Look up the class name and use that
292
+ VALUE klass = CLASS_OF(obj);
293
+ class_name = rb_class2name(klass);
294
+ if(klass == cTypedHash) {
295
+ VALUE orig_name = rb_funcall(obj, rb_intern("type"), 0);
296
+ class_name = RSTRING_PTR(orig_name);
297
+ } else if(type == T_HASH) {
298
+ // Don't bother looking up hash mapping, but need to check class name first in case it's a typed hash
299
+ return Qnil;
300
+ }
301
+ }
302
+
303
+ return mapset_as_lookup(map->mapset, class_name);
304
+ }
305
+
306
+ /*
307
+ * call_seq:
308
+ * mapper.get_ruby_obj => obj
309
+ *
310
+ * Instantiates a ruby object using the mapping configuration based on the
311
+ * source AS class name. If there is no mapping defined, it returns a
312
+ * <tt>RocketAMF::Values::TypedHash</tt> with the serialized class name.
313
+ */
314
+ static VALUE mapping_get_ruby_obj(VALUE self, VALUE name) {
315
+ CLASS_MAPPING *map;
316
+ Data_Get_Struct(self, CLASS_MAPPING, map);
317
+
318
+ VALUE argv[1];
319
+ VALUE ruby_class_name = mapset_rb_lookup(map->mapset, RSTRING_PTR(name));
320
+ if(ruby_class_name == Qnil) {
321
+ argv[0] = name;
322
+ return rb_class_new_instance(1, argv, cTypedHash);
323
+ } else {
324
+ VALUE base_const = rb_mKernel;
325
+ char* endptr;
326
+ char* ptr = RSTRING_PTR(ruby_class_name);
327
+ while((endptr = strstr(ptr,"::"))) {
328
+ endptr[0] = '\0'; // NULL terminate to make string ops work
329
+ base_const = rb_const_get(base_const, rb_intern(ptr));
330
+ endptr[0] = ':'; // Restore correct char
331
+ ptr = endptr + 2;
332
+ }
333
+ return rb_class_new_instance(0, NULL, rb_const_get(base_const, rb_intern(ptr)));
334
+ }
335
+ }
336
+
337
+ /*
338
+ * st_table iterator for populating a given object from a property hash
339
+ */
340
+ static int mapping_populate_iter(VALUE key, VALUE val, const VALUE args[2]) {
341
+ CLASS_MAPPING *map;
342
+ Data_Get_Struct(args[0], CLASS_MAPPING, map);
343
+ VALUE obj = args[1];
344
+
345
+ if(TYPE(obj) == T_HASH) {
346
+ rb_hash_aset(obj, key, val);
347
+ return ST_CONTINUE;
348
+ }
349
+
350
+ if(TYPE(key) != T_SYMBOL) rb_raise(rb_eArgError, "Invalid type for property key: %d", TYPE(key));
351
+
352
+ // Calculate symbol for setter function
353
+ ID key_id = SYM2ID(key);
354
+ ID setter_id;
355
+ if(!st_lookup(map->setter_cache, key_id, &setter_id)) {
356
+ // Calculate symbol
357
+ const char* key_str = rb_id2name(key_id);
358
+ long len = strlen(key_str);
359
+ char* setter = ALLOC_N(char, len+2);
360
+ memcpy(setter, key_str, len);
361
+ setter[len] = '=';
362
+ setter[len+1] = '\0';
363
+ setter_id = rb_intern(setter);
364
+ xfree(setter);
365
+
366
+ // Store it
367
+ st_add_direct(map->setter_cache, key_id, setter_id);
368
+ }
369
+
370
+ if(rb_respond_to(obj, setter_id)) {
371
+ rb_funcall(obj, setter_id, 1, val);
372
+ } else if(rb_respond_to(obj, id_hashset)) {
373
+ rb_funcall(obj, id_hashset, 2, key, val);
374
+ }
375
+
376
+ return ST_CONTINUE;
377
+ }
378
+
379
+ /*
380
+ * call-seq:
381
+ * mapper.populate_ruby_obj(obj, props, dynamic_props=nil) => obj
382
+ *
383
+ * Populates the ruby object using the given properties. Property hashes MUST
384
+ * have symbol keys, or it will raise an exception.
385
+ */
386
+ static VALUE mapping_populate(int argc, VALUE *argv, VALUE self) {
387
+ // Check args
388
+ VALUE obj, props, dynamic_props;
389
+ rb_scan_args(argc, argv, "21", &obj, &props, &dynamic_props);
390
+
391
+ VALUE args[2] = {self, obj};
392
+ st_foreach(RHASH_TBL(props), mapping_populate_iter, (st_data_t)args);
393
+ if(dynamic_props != Qnil) {
394
+ st_foreach(RHASH_TBL(dynamic_props), mapping_populate_iter, (st_data_t)args);
395
+ }
396
+
397
+ return obj;
398
+ }
399
+
400
+ /*
401
+ * call-seq:
402
+ * mapper.props_for_serialization(obj) => hash
403
+ *
404
+ * Extracts all exportable properties from the given ruby object and returns
405
+ * them in a hash. For performance purposes, property detection is only performed
406
+ * once for a given class instance, and then cached for all instances of that
407
+ * class. IF YOU'RE ADDING AND REMOVING PROPERTIES FROM CLASS INSTANCES YOU
408
+ * CANNOT USE THE FAST CLASS MAPPER.
409
+ */
410
+ static VALUE mapping_props(VALUE self, VALUE obj) {
411
+ CLASS_MAPPING *map;
412
+ Data_Get_Struct(self, CLASS_MAPPING, map);
413
+
414
+ if(TYPE(obj) == T_HASH) {
415
+ return obj;
416
+ }
417
+
418
+ // Get "properties"
419
+ VALUE props_ary;
420
+ VALUE klass = CLASS_OF(obj);
421
+ long i, len;
422
+ if(!st_lookup(map->prop_cache, klass, &props_ary)) {
423
+ props_ary = rb_ary_new();
424
+
425
+ // Build props array
426
+ VALUE all_methods = rb_class_public_instance_methods(0, NULL, klass);
427
+ VALUE object_methods = rb_class_public_instance_methods(0, NULL, rb_cObject);
428
+ VALUE possible_methods = rb_funcall(all_methods, rb_intern("-"), 1, object_methods);
429
+ len = RARRAY_LEN(possible_methods);
430
+ for(i = 0; i < len; i++) {
431
+ VALUE meth = rb_obj_method(obj, RARRAY_PTR(possible_methods)[i]);
432
+ VALUE arity = rb_funcall(meth, rb_intern("arity"), 0);
433
+ if(FIX2INT(arity) == 0) {
434
+ rb_ary_push(props_ary, RARRAY_PTR(possible_methods)[i]);
435
+ }
436
+ }
437
+
438
+ // Store it
439
+ st_add_direct(map->prop_cache, klass, props_ary);
440
+ }
441
+
442
+ // Build properties hash using list of properties
443
+ VALUE props = rb_hash_new();
444
+ len = RARRAY_LEN(props_ary);
445
+ for(i = 0; i < len; i++) {
446
+ VALUE key = RARRAY_PTR(props_ary)[i];
447
+ ID getter = (TYPE(key) == T_STRING) ? rb_intern(RSTRING_PTR(key)) : SYM2ID(key);
448
+ rb_hash_aset(props, key, rb_funcall(obj, getter, 0));
449
+ }
450
+
451
+ return props;
452
+ }
453
+
454
+ void Init_rocket_amf_fast_class_mapping() {
455
+ // Define map set
456
+ cFastMappingSet = rb_define_class_under(mRocketAMFExt, "FastMappingSet", rb_cObject);
457
+ rb_define_alloc_func(cFastMappingSet, mapset_alloc);
458
+ rb_define_method(cFastMappingSet, "initialize", mapset_init, 0);
459
+ rb_define_method(cFastMappingSet, "map_defaults", mapset_map_defaults, 0);
460
+ rb_define_method(cFastMappingSet, "map", mapset_map, 1);
461
+
462
+ // Define FastClassMapping
463
+ VALUE cFastClassMapping = rb_define_class_under(mRocketAMFExt, "FastClassMapping", rb_cObject);
464
+ rb_define_alloc_func(cFastClassMapping, mapping_alloc);
465
+ rb_define_singleton_method(cFastClassMapping, "use_array_collection", mapping_s_array_collection_get, 0);
466
+ rb_define_singleton_method(cFastClassMapping, "use_array_collection=", mapping_s_array_collection_set, 1);
467
+ rb_define_singleton_method(cFastClassMapping, "mappings", mapping_s_mappings, 0);
468
+ rb_define_singleton_method(cFastClassMapping, "reset", mapping_s_reset, 0);
469
+ rb_define_singleton_method(cFastClassMapping, "define", mapping_s_define, 0);
470
+ rb_define_attr(cFastClassMapping, "use_array_collection", 1, 0);
471
+ rb_define_method(cFastClassMapping, "initialize", mapping_init, 0);
472
+ rb_define_method(cFastClassMapping, "get_as_class_name", mapping_as_class_name, 1);
473
+ rb_define_method(cFastClassMapping, "get_ruby_obj", mapping_get_ruby_obj, 1);
474
+ rb_define_method(cFastClassMapping, "populate_ruby_obj", mapping_populate, -1);
475
+ rb_define_method(cFastClassMapping, "props_for_serialization", mapping_props, 1);
476
+
477
+ // Cache values
478
+ cTypedHash = rb_const_get(rb_const_get(mRocketAMF, rb_intern("Values")), rb_intern("TypedHash"));
479
+ id_use_ac = rb_intern("use_array_collection");
480
+ id_use_ac_ivar = rb_intern("@use_array_collection");
481
+ id_mappings = rb_intern("mappings");
482
+ id_mappings_ivar = rb_intern("@mappings");
483
+ id_hashset = rb_intern("[]=");
484
+ }