mrpin-amf 2.1.8

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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/README.rdoc +52 -0
  4. data/Rakefile +59 -0
  5. data/benchmark.rb +86 -0
  6. data/doc/amf3-speification.pdf +0 -0
  7. data/ext/rocketamf_ext/class_mapping.c +483 -0
  8. data/ext/rocketamf_ext/constants.h +52 -0
  9. data/ext/rocketamf_ext/deserializer.c +776 -0
  10. data/ext/rocketamf_ext/deserializer.h +28 -0
  11. data/ext/rocketamf_ext/extconf.rb +18 -0
  12. data/ext/rocketamf_ext/remoting.c +184 -0
  13. data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
  14. data/ext/rocketamf_ext/serializer.c +834 -0
  15. data/ext/rocketamf_ext/serializer.h +29 -0
  16. data/ext/rocketamf_ext/utility.h +4 -0
  17. data/lib/amf/common/hash_with_type.rb +20 -0
  18. data/lib/amf/ext.rb +22 -0
  19. data/lib/amf/pure/amf_constants.rb +42 -0
  20. data/lib/amf/pure/deserializer.rb +354 -0
  21. data/lib/amf/pure/errors/all_files.rb +2 -0
  22. data/lib/amf/pure/errors/amf_error.rb +5 -0
  23. data/lib/amf/pure/errors/amf_error_incomplete.rb +5 -0
  24. data/lib/amf/pure/helpers/all_files.rb +8 -0
  25. data/lib/amf/pure/helpers/cache_objects.rb +18 -0
  26. data/lib/amf/pure/helpers/cache_strings.rb +14 -0
  27. data/lib/amf/pure/helpers/io_helper_base.rb +19 -0
  28. data/lib/amf/pure/helpers/io_helper_read.rb +67 -0
  29. data/lib/amf/pure/helpers/io_helper_write.rb +49 -0
  30. data/lib/amf/pure/mapping/class_mapper.rb +159 -0
  31. data/lib/amf/pure/mapping/mapping_set.rb +49 -0
  32. data/lib/amf/pure/serializer.rb +318 -0
  33. data/lib/amf/pure.rb +16 -0
  34. data/lib/amf.rb +140 -0
  35. data/mrpin-amf.gemspec +24 -0
  36. data/spec/fixtures/objects/complex/amf3-associative-array.bin +1 -0
  37. data/spec/fixtures/objects/complex/amf3-byte-array.bin +0 -0
  38. data/spec/fixtures/objects/complex/amf3-date.bin +0 -0
  39. data/spec/fixtures/objects/complex/amf3-dictionary.bin +0 -0
  40. data/spec/fixtures/objects/complex/amf3-dynamic-object.bin +2 -0
  41. data/spec/fixtures/objects/complex/amf3-empty-array.bin +1 -0
  42. data/spec/fixtures/objects/complex/amf3-empty-dictionary.bin +0 -0
  43. data/spec/fixtures/objects/complex/amf3-hash.bin +2 -0
  44. data/spec/fixtures/objects/complex/amf3-mixed-array.bin +10 -0
  45. data/spec/fixtures/objects/complex/amf3-primitive-array.bin +1 -0
  46. data/spec/fixtures/objects/complex/amf3-typed-object.bin +2 -0
  47. data/spec/fixtures/objects/encoding/amf3-complex-encoded-string-array.bin +1 -0
  48. data/spec/fixtures/objects/encoding/amf3-encoded-string-ref.bin +0 -0
  49. data/spec/fixtures/objects/references/amf3-array-ref.bin +1 -0
  50. data/spec/fixtures/objects/references/amf3-byte-array-ref.bin +1 -0
  51. data/spec/fixtures/objects/references/amf3-date-ref.bin +0 -0
  52. data/spec/fixtures/objects/references/amf3-empty-array-ref.bin +1 -0
  53. data/spec/fixtures/objects/references/amf3-empty-string-ref.bin +1 -0
  54. data/spec/fixtures/objects/references/amf3-graph-member.bin +0 -0
  55. data/spec/fixtures/objects/references/amf3-object-ref.bin +0 -0
  56. data/spec/fixtures/objects/references/amf3-string-ref.bin +0 -0
  57. data/spec/fixtures/objects/references/amf3-trait-ref.bin +3 -0
  58. data/spec/fixtures/objects/simple/amf3-0.bin +0 -0
  59. data/spec/fixtures/objects/simple/amf3-bigNum.bin +0 -0
  60. data/spec/fixtures/objects/simple/amf3-false.bin +1 -0
  61. data/spec/fixtures/objects/simple/amf3-float.bin +0 -0
  62. data/spec/fixtures/objects/simple/amf3-large-max.bin +0 -0
  63. data/spec/fixtures/objects/simple/amf3-large-min.bin +0 -0
  64. data/spec/fixtures/objects/simple/amf3-max.bin +1 -0
  65. data/spec/fixtures/objects/simple/amf3-min.bin +0 -0
  66. data/spec/fixtures/objects/simple/amf3-null.bin +1 -0
  67. data/spec/fixtures/objects/simple/amf3-string.bin +1 -0
  68. data/spec/fixtures/objects/simple/amf3-symbol.bin +1 -0
  69. data/spec/fixtures/objects/simple/amf3-true.bin +1 -0
  70. data/spec/helpers/class_mapping_test.rb +4 -0
  71. data/spec/helpers/class_mapping_test2.rb +3 -0
  72. data/spec/helpers/fixtures.rb +34 -0
  73. data/spec/helpers/other_class.rb +4 -0
  74. data/spec/helpers/ruby_class.rb +4 -0
  75. data/spec/helpers/test_ruby_class.rb +4 -0
  76. data/spec/spec-class_mapping.rb +98 -0
  77. data/spec/spec_deserializer.rb +239 -0
  78. data/spec/spec_fast_class_mapping.rb +147 -0
  79. data/spec/spec_helper.rb +8 -0
  80. data/spec/spec_serializer.rb +267 -0
  81. metadata +146 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0eb9baf5a4c82fcbed7895ab596febba4a04b32c
4
+ data.tar.gz: 4abe64977530f441bd0248d08438746a8318ec10
5
+ SHA512:
6
+ metadata.gz: d92c60357b8af4ca5004e2d7b6dd42e9897b428d63ab3ef91a93fd0a562b57c959f9884e94f95a480f85b1a8d968464d98af7109ff983d607fdd2f97262223c3
7
+ data.tar.gz: b273f9f83c3d2411d9bb052d6bab2a38223b3cc5a615ea2a8864191d0f871aff8fe635df208c371a5825ea039020bdeba4a72016bac69ebe01f2f652fc9f2255
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ pkg/*
2
+ .loadpath
3
+ .project
4
+ /rdoc/
5
+ *.bundle
6
+ *.so
7
+ *.dll
8
+ /tmp
9
+ *.swf
data/README.rdoc ADDED
@@ -0,0 +1,52 @@
1
+ == DESCRIPTION:
2
+
3
+
4
+ #todo:
5
+ -implement remote procedure call
6
+ -implement c version
7
+
8
+ mprin-amf is a full featured AMF3 serializer and deserializer based on RocketAMF library with support for
9
+ bi-directional Flash to Ruby class mapping, custom serialization and mapping,
10
+ remoting gateway helpers that follow AMF3 messaging specs, and a suite of specs
11
+ to ensure adherence to the specification documents put out by Adobe. If the C
12
+ components compile, then RocketAMF automatically takes advantage of them to
13
+ provide a substantial performance benefit. In addition, mrpin-amf is fully
14
+ compatible with Ruby 2.0+.
15
+
16
+ == INSTALL:
17
+
18
+ gem install mrpin-amf
19
+
20
+ == SIMPLE EXAMPLE:
21
+
22
+ require 'rocketamf'
23
+
24
+ hash = {:apple => "Apfel", :red => "Rot", :eyes => "Augen"}
25
+ File.open("amf.dat", 'w') do |f|
26
+ f.write RocketAMF.serialize(hash) # Use AMF3 encoding to serialize
27
+ end
28
+
29
+ == LICENSE:
30
+
31
+ (The MIT License)
32
+
33
+ Copyright (c) 2011 Stephen Augenstein and Jacob Henry
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining
36
+ a copy of this software and associated documentation files (the
37
+ 'Software'), to deal in the Software without restriction, including
38
+ without limitation the rights to use, copy, modify, merge, publish,
39
+ distribute, sublicense, and/or sell copies of the Software, and to
40
+ permit persons to whom the Software is furnished to do so, subject to
41
+ the following conditions:
42
+
43
+ The above copyright notice and this permission notice shall be
44
+ included in all copies or substantial portions of the Software.
45
+
46
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
47
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
48
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
49
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
50
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
51
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
52
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rdoc/task'
4
+ require 'rubygems/package_task'
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 = 'mrpin-amf.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
+ Gem::PackageTask.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/
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
data/benchmark.rb ADDED
@@ -0,0 +1,86 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/ext')
2
+ $:.unshift(File.dirname(__FILE__) + '/lib')
3
+ require 'rubygems'
4
+ require 'amf'
5
+ require 'amf/pure/deserializer' # Only ext gets included by default if available
6
+ require 'amf/pure/serializer'
7
+
8
+ OBJECT_COUNT = 100000
9
+ TESTS = 5
10
+
11
+ class TestClass
12
+ attr_accessor :prop_a
13
+ attr_accessor :prop_b
14
+ attr_accessor :prop_c
15
+ attr_accessor :prop_d
16
+ attr_accessor :prop_e
17
+
18
+ def populate(some_arg = nil) # Make sure class mapper doesn't think populate is a property
19
+ @@count ||= 1
20
+ @prop_a = "asdfasdf #{@@count}"
21
+ @prop_b = 'simple string'
22
+ @prop_c = 3120094.03
23
+ @prop_d = Time.now
24
+ @prop_e = 3120094
25
+ @@count += 1
26
+ self
27
+ end
28
+ end
29
+
30
+ objs = []
31
+ OBJECT_COUNT.times do
32
+ objs << TestClass.new.populate
33
+ end
34
+
35
+ #todo: add native
36
+ %w( pure).each do |type|
37
+
38
+ use_ruby_version = type == 'pure'
39
+
40
+ # Set up class mapper
41
+ class_mapper = nil
42
+
43
+ if use_ruby_version
44
+ class_mapper = AMF::ClassMapper
45
+ else
46
+ class_mapper = AMF::Ext::FastClassMapping
47
+ end
48
+
49
+ class_mapper.define do |m|
50
+ m.map as: 'TestClass', ruby: 'TestClass'
51
+ end
52
+
53
+
54
+ # 2**24 is larger than anyone is ever going to run this for
55
+ min_serialize = 2**24
56
+ min_deserialize = 2**24
57
+
58
+ puts "Testing #{type} AMF3"
59
+ TESTS.times do
60
+ serializer = nil
61
+ deserializer = nil
62
+
63
+ if use_ruby_version
64
+ serializer = AMF::Pure::Serializer.new(class_mapper.new)
65
+ deserializer = AMF::Pure::Deserializer.new(class_mapper.new)
66
+ else
67
+ serializer = AMF::Ext::Serializer.new(class_mapper.new)
68
+ deserializer = AMF::Ext::Deserializer.new(class_mapper.new)
69
+ end
70
+
71
+ start_time = Time.now
72
+ out = serializer.serialize(objs)
73
+ end_time = Time.now
74
+ puts "\tserialize run: #{end_time-start_time}s"
75
+ min_serialize = [end_time-start_time, min_serialize].min
76
+
77
+ start_time = Time.now
78
+ temp = deserializer.deserialize(out)
79
+ end_time = Time.now
80
+ puts "\tdeserialize run: #{end_time-start_time}s"
81
+ min_deserialize = [end_time-start_time, min_deserialize].min
82
+ end
83
+
84
+ puts "\tminimum serialize time: #{min_serialize}s"
85
+ puts "\tminimum deserialize time: #{min_deserialize}s"
86
+ end
Binary file
@@ -0,0 +1,483 @@
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::Types::AbstractMessage",
92
+ "RocketAMF::Types::RemotingMessage",
93
+ "RocketAMF::Types::AsyncMessage",
94
+ "RocketAMF::Types::AsyncMessageExt",
95
+ "RocketAMF::Types::CommandMessage",
96
+ "RocketAMF::Types::CommandMessageExt",
97
+ "RocketAMF::Types::AcknowledgeMessageExt",
98
+ "RocketAMF::Types::ErrorMessage"
99
+ };
100
+ const char* as_classes[] = {
101
+ "flex.messaging.messages.AbstractMessage",
102
+ "flex.messaging.messages.RemotingMessage",
103
+ "flex.messaging.messages.AsyncMessage",
104
+ "DSA",
105
+ "flex.messaging.messages.CommandMessage",
106
+ "DSC",
107
+ "flex.messaging.messages.AcknowledgeMessage",
108
+ "DSK",
109
+ "flex.messaging.messages.ErrorMessage"
110
+ };
111
+
112
+ int i;
113
+ ID map_id = rb_intern("map");
114
+ VALUE params = rb_hash_new();
115
+ VALUE as_sym = ID2SYM(rb_intern("as"));
116
+ VALUE ruby_sym = ID2SYM(rb_intern("ruby"));
117
+ for(i = 0; i < NUM_MAPPINGS; i++) {
118
+ rb_hash_aset(params, as_sym, rb_str_new2(as_classes[i]));
119
+ rb_hash_aset(params, ruby_sym, rb_str_new2(ruby_classes[i]));
120
+ rb_funcall(self, map_id, 1, params);
121
+ }
122
+
123
+ return self;
124
+ }
125
+
126
+ /*
127
+ * call-seq:
128
+ * m.map :as => 'com.example.Date', :ruby => "Example::Date'
129
+ *
130
+ * Map a given AS class to a ruby class. Use fully qualified names for both.
131
+ */
132
+ static VALUE mapset_map(VALUE self, VALUE mapping) {
133
+ MAPSET *set;
134
+ Data_Get_Struct(self, MAPSET, set);
135
+
136
+ VALUE as_class = rb_hash_aref(mapping, ID2SYM(rb_intern("as")));
137
+ VALUE rb_class = rb_hash_aref(mapping, ID2SYM(rb_intern("ruby")));
138
+ st_insert(set->as_mappings, (st_data_t)strdup(RSTRING_PTR(as_class)), rb_class);
139
+ st_insert(set->rb_mappings, (st_data_t)strdup(RSTRING_PTR(rb_class)), as_class);
140
+
141
+ return Qnil;
142
+ }
143
+
144
+ /*
145
+ * Internal method for looking up a given ruby class's AS class name or Qnil if
146
+ * not found
147
+ */
148
+ static VALUE mapset_as_lookup(VALUE self, const char* class_name) {
149
+ MAPSET *set;
150
+ Data_Get_Struct(self, MAPSET, set);
151
+
152
+ VALUE as_name;
153
+ if(st_lookup(set->rb_mappings, (st_data_t)class_name, &as_name)) {
154
+ return as_name;
155
+ } else {
156
+ return Qnil;
157
+ }
158
+ }
159
+
160
+ /*
161
+ * Internal method for looking up a given AS class names ruby class name mapping
162
+ * or Qnil if not found
163
+ */
164
+ static VALUE mapset_rb_lookup(VALUE self, const char* class_name) {
165
+ MAPSET *set;
166
+ Data_Get_Struct(self, MAPSET, set);
167
+
168
+ VALUE rb_name;
169
+ if(st_lookup(set->as_mappings, (st_data_t)class_name, &rb_name)) {
170
+ return rb_name;
171
+ } else {
172
+ return Qnil;
173
+ }
174
+ }
175
+
176
+ /*
177
+ * Mark the mapset object and property lookup cache
178
+ */
179
+ static void mapping_mark(CLASS_MAPPING *map) {
180
+ if(!map) return;
181
+ rb_gc_mark(map->mapset);
182
+ rb_mark_tbl(map->prop_cache);
183
+ }
184
+
185
+ /*
186
+ * Free prop cache table and struct
187
+ */
188
+ static void mapping_free(CLASS_MAPPING *map) {
189
+ st_free_table(map->setter_cache);
190
+ st_free_table(map->prop_cache);
191
+ xfree(map);
192
+ }
193
+
194
+ /*
195
+ * Allocate class mapping struct
196
+ */
197
+ static VALUE mapping_alloc(VALUE klass) {
198
+ CLASS_MAPPING *map = ALLOC(CLASS_MAPPING);
199
+ memset(map, 0, sizeof(CLASS_MAPPING));
200
+ VALUE self = Data_Wrap_Struct(klass, mapping_mark, mapping_free, map);
201
+ map->setter_cache = st_init_numtable();
202
+ map->prop_cache = st_init_numtable();
203
+ return self;
204
+ }
205
+
206
+ /*
207
+ * Class-level getter for use_array_collection
208
+ */
209
+ static VALUE mapping_s_array_collection_get(VALUE klass) {
210
+ VALUE use_ac = rb_ivar_get(klass, id_use_ac_ivar);
211
+ if(use_ac == Qnil) {
212
+ use_ac = Qfalse;
213
+ rb_ivar_set(klass, id_use_ac_ivar, use_ac);
214
+ }
215
+ return use_ac;
216
+ }
217
+
218
+ /*
219
+ * Class-level setter for use_array_collection
220
+ */
221
+ static VALUE mapping_s_array_collection_set(VALUE klass, VALUE use_ac) {
222
+ return rb_ivar_set(klass, id_use_ac_ivar, use_ac);
223
+ }
224
+
225
+ /*
226
+ * Return MappingSet for class mapper, creating if uninitialized
227
+ */
228
+ static VALUE mapping_s_mappings(VALUE klass) {
229
+ VALUE mappings = rb_ivar_get(klass, id_mappings_ivar);
230
+ if(mappings == Qnil) {
231
+ mappings = rb_class_new_instance(0, NULL, cFastMappingSet);
232
+ rb_ivar_set(klass, id_mappings_ivar, mappings);
233
+ }
234
+ return mappings;
235
+ }
236
+
237
+ /*
238
+ * call-seq:
239
+ * mapper.define {|m| block } => nil
240
+ *
241
+ * Define class mappings in the block. Block is passed a MappingSet object as
242
+ * the first parameter. See RocketAMF::ClassMapping for details.
243
+ */
244
+ static VALUE mapping_s_define(VALUE klass) {
245
+ if (rb_block_given_p()) {
246
+ VALUE mappings = rb_funcall(klass, id_mappings, 0);
247
+ rb_yield(mappings);
248
+ }
249
+ return Qnil;
250
+ }
251
+
252
+ /*
253
+ * Reset class mappings
254
+ */
255
+ static VALUE mapping_s_reset(VALUE klass) {
256
+ rb_ivar_set(klass, id_use_ac_ivar, Qfalse);
257
+ rb_ivar_set(klass, id_mappings_ivar, Qnil);
258
+ return Qnil;
259
+ }
260
+
261
+ /*
262
+ * Initialize class mapping object, setting use_class_mapping to false
263
+ */
264
+ static VALUE mapping_init(VALUE self) {
265
+ CLASS_MAPPING *map;
266
+ Data_Get_Struct(self, CLASS_MAPPING, map);
267
+ map->mapset = rb_funcall(CLASS_OF(self), id_mappings, 0);
268
+ VALUE use_ac = rb_funcall(CLASS_OF(self), id_use_ac, 0);
269
+ rb_ivar_set(self, id_use_ac_ivar, use_ac);
270
+ return self;
271
+ }
272
+
273
+ /*
274
+ * call-seq:
275
+ * mapper.get_as_class_name => str
276
+ *
277
+ * Returns the AS class name for the given ruby object. Will also take a string
278
+ * containing the ruby class name.
279
+ */
280
+ static VALUE mapping_as_class_name(VALUE self, VALUE obj) {
281
+ CLASS_MAPPING *map;
282
+ Data_Get_Struct(self, CLASS_MAPPING, map);
283
+
284
+ int type = TYPE(obj);
285
+ const char* class_name;
286
+ if(type == T_STRING) {
287
+ // Use strings as the class name
288
+ class_name = RSTRING_PTR(obj);
289
+ } else {
290
+ // Look up the class name and use that
291
+ VALUE klass = CLASS_OF(obj);
292
+ class_name = rb_class2name(klass);
293
+ if(klass == cTypedHash) {
294
+ VALUE orig_name = rb_funcall(obj, rb_intern("type"), 0);
295
+ class_name = RSTRING_PTR(orig_name);
296
+ } else if(type == T_HASH) {
297
+ // Don't bother looking up hash mapping, but need to check class name first in case it's a typed hash
298
+ return Qnil;
299
+ }
300
+ }
301
+
302
+ return mapset_as_lookup(map->mapset, class_name);
303
+ }
304
+
305
+ /*
306
+ * call_seq:
307
+ * mapper.get_ruby_obj => obj
308
+ *
309
+ * Instantiates a ruby object using the mapping configuration based on the
310
+ * source AS class name. If there is no mapping defined, it returns a
311
+ * <tt>RocketAMF::Types::TypedHash</tt> with the serialized class name.
312
+ */
313
+ static VALUE mapping_get_ruby_obj(VALUE self, VALUE name) {
314
+ CLASS_MAPPING *map;
315
+ Data_Get_Struct(self, CLASS_MAPPING, map);
316
+
317
+ VALUE argv[1];
318
+ VALUE ruby_class_name = mapset_rb_lookup(map->mapset, RSTRING_PTR(name));
319
+ if(ruby_class_name == Qnil) {
320
+ argv[0] = name;
321
+ return rb_class_new_instance(1, argv, cTypedHash);
322
+ } else {
323
+ VALUE base_const = rb_mKernel;
324
+ char* endptr;
325
+ char* ptr = RSTRING_PTR(ruby_class_name);
326
+ while((endptr = strstr(ptr,"::"))) {
327
+ endptr[0] = '\0'; // NULL terminate to make string ops work
328
+ base_const = rb_const_get(base_const, rb_intern(ptr));
329
+ endptr[0] = ':'; // Restore correct char
330
+ ptr = endptr + 2;
331
+ }
332
+ return rb_class_new_instance(0, NULL, rb_const_get(base_const, rb_intern(ptr)));
333
+ }
334
+ }
335
+
336
+ /*
337
+ * st_table iterator for populating a given object from a property hash
338
+ */
339
+ static int mapping_populate_iter(VALUE key, VALUE val, const VALUE args[2]) {
340
+ CLASS_MAPPING *map;
341
+ Data_Get_Struct(args[0], CLASS_MAPPING, map);
342
+ VALUE obj = args[1];
343
+
344
+ if(TYPE(obj) == T_HASH) {
345
+ rb_hash_aset(obj, key, val);
346
+ return ST_CONTINUE;
347
+ }
348
+
349
+ if(TYPE(key) != T_SYMBOL) rb_raise(rb_eArgError, "Invalid type for property key: %d", TYPE(key));
350
+
351
+ // Calculate symbol for setter function
352
+ ID key_id = SYM2ID(key);
353
+ ID setter_id;
354
+ if(!st_lookup(map->setter_cache, key_id, &setter_id)) {
355
+ // Calculate symbol
356
+ const char* key_str = rb_id2name(key_id);
357
+ long len = strlen(key_str);
358
+ char* setter = ALLOC_N(char, len+2);
359
+ memcpy(setter, key_str, len);
360
+ setter[len] = '=';
361
+ setter[len+1] = '\0';
362
+ setter_id = rb_intern(setter);
363
+ xfree(setter);
364
+
365
+ // Store it
366
+ st_add_direct(map->setter_cache, key_id, setter_id);
367
+ }
368
+
369
+ if(rb_respond_to(obj, setter_id)) {
370
+ rb_funcall(obj, setter_id, 1, val);
371
+ } else if(rb_respond_to(obj, id_hashset)) {
372
+ rb_funcall(obj, id_hashset, 2, key, val);
373
+ }
374
+
375
+ return ST_CONTINUE;
376
+ }
377
+
378
+ /*
379
+ * call-seq:
380
+ * mapper.populate_ruby_obj(obj, props, dynamic_props=nil) => obj
381
+ *
382
+ * Populates the ruby object using the given properties. Property hashes MUST
383
+ * have symbol keys, or it will raise an exception.
384
+ */
385
+ static VALUE mapping_populate(int argc, VALUE *argv, VALUE self) {
386
+ // Check args
387
+ VALUE obj, props, dynamic_props;
388
+ rb_scan_args(argc, argv, "21", &obj, &props, &dynamic_props);
389
+
390
+ VALUE args[2] = {self, obj};
391
+ st_foreach(RHASH_TBL(props), mapping_populate_iter, (st_data_t)args);
392
+ if(dynamic_props != Qnil) {
393
+ st_foreach(RHASH_TBL(dynamic_props), mapping_populate_iter, (st_data_t)args);
394
+ }
395
+
396
+ return obj;
397
+ }
398
+
399
+ /*
400
+ * call-seq:
401
+ * mapper.props_for_serialization(obj) => hash
402
+ *
403
+ * Extracts all exportable properties from the given ruby object and returns
404
+ * them in a hash. For performance purposes, property detection is only performed
405
+ * once for a given class instance, and then cached for all instances of that
406
+ * class. IF YOU'RE ADDING AND REMOVING PROPERTIES FROM CLASS INSTANCES YOU
407
+ * CANNOT USE THE FAST CLASS MAPPER.
408
+ */
409
+ static VALUE mapping_props(VALUE self, VALUE obj) {
410
+ CLASS_MAPPING *map;
411
+ Data_Get_Struct(self, CLASS_MAPPING, map);
412
+
413
+ if(TYPE(obj) == T_HASH) {
414
+ return obj;
415
+ }
416
+
417
+ // Get "properties"
418
+ VALUE props_ary;
419
+ VALUE klass = CLASS_OF(obj);
420
+ long i, len;
421
+ if(!st_lookup(map->prop_cache, klass, &props_ary)) {
422
+ props_ary = rb_ary_new();
423
+
424
+ // Build props array
425
+ VALUE all_methods = rb_class_public_instance_methods(0, NULL, klass);
426
+ VALUE object_methods = rb_class_public_instance_methods(0, NULL, rb_cObject);
427
+ VALUE possible_methods = rb_funcall(all_methods, rb_intern("-"), 1, object_methods);
428
+ len = RARRAY_LEN(possible_methods);
429
+ for(i = 0; i < len; i++) {
430
+ VALUE meth = rb_obj_method(obj, RARRAY_PTR(possible_methods)[i]);
431
+ VALUE arity = rb_funcall(meth, rb_intern("arity"), 0);
432
+ if(FIX2INT(arity) == 0) {
433
+ rb_ary_push(props_ary, RARRAY_PTR(possible_methods)[i]);
434
+ }
435
+ }
436
+
437
+ // Store it
438
+ st_add_direct(map->prop_cache, klass, props_ary);
439
+ }
440
+
441
+ // Build properties hash using list of properties
442
+ VALUE props = rb_hash_new();
443
+ len = RARRAY_LEN(props_ary);
444
+ for(i = 0; i < len; i++) {
445
+ VALUE key = RARRAY_PTR(props_ary)[i];
446
+ ID getter = (TYPE(key) == T_STRING) ? rb_intern(RSTRING_PTR(key)) : SYM2ID(key);
447
+ rb_hash_aset(props, key, rb_funcall(obj, getter, 0));
448
+ }
449
+
450
+ return props;
451
+ }
452
+
453
+ void Init_rocket_amf_fast_class_mapping() {
454
+ // Define map set
455
+ cFastMappingSet = rb_define_class_under(mRocketAMFExt, "FastMappingSet", rb_cObject);
456
+ rb_define_alloc_func(cFastMappingSet, mapset_alloc);
457
+ rb_define_method(cFastMappingSet, "initialize", mapset_init, 0);
458
+ rb_define_method(cFastMappingSet, "map_defaults", mapset_map_defaults, 0);
459
+ rb_define_method(cFastMappingSet, "map", mapset_map, 1);
460
+
461
+ // Define FastClassMapping
462
+ VALUE cFastClassMapping = rb_define_class_under(mRocketAMFExt, "FastClassMapping", rb_cObject);
463
+ rb_define_alloc_func(cFastClassMapping, mapping_alloc);
464
+ rb_define_singleton_method(cFastClassMapping, "use_array_collection", mapping_s_array_collection_get, 0);
465
+ rb_define_singleton_method(cFastClassMapping, "use_array_collection=", mapping_s_array_collection_set, 1);
466
+ rb_define_singleton_method(cFastClassMapping, "mappings", mapping_s_mappings, 0);
467
+ rb_define_singleton_method(cFastClassMapping, "reset", mapping_s_reset, 0);
468
+ rb_define_singleton_method(cFastClassMapping, "define", mapping_s_define, 0);
469
+ rb_define_attr(cFastClassMapping, "use_array_collection", 1, 0);
470
+ rb_define_method(cFastClassMapping, "initialize", mapping_init, 0);
471
+ rb_define_method(cFastClassMapping, "get_as_class_name", mapping_as_class_name, 1);
472
+ rb_define_method(cFastClassMapping, "get_ruby_obj", mapping_get_ruby_obj, 1);
473
+ rb_define_method(cFastClassMapping, "populate_ruby_obj", mapping_populate, -1);
474
+ rb_define_method(cFastClassMapping, "props_for_serialization", mapping_props, 1);
475
+
476
+ // Cache values
477
+ cTypedHash = rb_const_get(rb_const_get(mRocketAMF, rb_intern("Types")), rb_intern("TypedHash"));
478
+ id_use_ac = rb_intern("use_array_collection");
479
+ id_use_ac_ivar = rb_intern("@use_array_collection");
480
+ id_mappings = rb_intern("mappings");
481
+ id_mappings_ivar = rb_intern("@mappings");
482
+ id_hashset = rb_intern("[]=");
483
+ }