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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/README.rdoc +52 -0
- data/Rakefile +59 -0
- data/benchmark.rb +86 -0
- data/doc/amf3-speification.pdf +0 -0
- data/ext/rocketamf_ext/class_mapping.c +483 -0
- data/ext/rocketamf_ext/constants.h +52 -0
- data/ext/rocketamf_ext/deserializer.c +776 -0
- data/ext/rocketamf_ext/deserializer.h +28 -0
- data/ext/rocketamf_ext/extconf.rb +18 -0
- data/ext/rocketamf_ext/remoting.c +184 -0
- data/ext/rocketamf_ext/rocketamf_ext.c +38 -0
- data/ext/rocketamf_ext/serializer.c +834 -0
- data/ext/rocketamf_ext/serializer.h +29 -0
- data/ext/rocketamf_ext/utility.h +4 -0
- data/lib/amf/common/hash_with_type.rb +20 -0
- data/lib/amf/ext.rb +22 -0
- data/lib/amf/pure/amf_constants.rb +42 -0
- data/lib/amf/pure/deserializer.rb +354 -0
- data/lib/amf/pure/errors/all_files.rb +2 -0
- data/lib/amf/pure/errors/amf_error.rb +5 -0
- data/lib/amf/pure/errors/amf_error_incomplete.rb +5 -0
- data/lib/amf/pure/helpers/all_files.rb +8 -0
- data/lib/amf/pure/helpers/cache_objects.rb +18 -0
- data/lib/amf/pure/helpers/cache_strings.rb +14 -0
- data/lib/amf/pure/helpers/io_helper_base.rb +19 -0
- data/lib/amf/pure/helpers/io_helper_read.rb +67 -0
- data/lib/amf/pure/helpers/io_helper_write.rb +49 -0
- data/lib/amf/pure/mapping/class_mapper.rb +159 -0
- data/lib/amf/pure/mapping/mapping_set.rb +49 -0
- data/lib/amf/pure/serializer.rb +318 -0
- data/lib/amf/pure.rb +16 -0
- data/lib/amf.rb +140 -0
- data/mrpin-amf.gemspec +24 -0
- data/spec/fixtures/objects/complex/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/complex/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-date.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/complex/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/complex/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/complex/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/complex/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/complex/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/complex/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/encoding/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/encoding/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/references/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/references/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/references/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/simple/amf3-0.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-false.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-float.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-max.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-min.bin +0 -0
- data/spec/fixtures/objects/simple/amf3-null.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-string.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/simple/amf3-true.bin +1 -0
- data/spec/helpers/class_mapping_test.rb +4 -0
- data/spec/helpers/class_mapping_test2.rb +3 -0
- data/spec/helpers/fixtures.rb +34 -0
- data/spec/helpers/other_class.rb +4 -0
- data/spec/helpers/ruby_class.rb +4 -0
- data/spec/helpers/test_ruby_class.rb +4 -0
- data/spec/spec-class_mapping.rb +98 -0
- data/spec/spec_deserializer.rb +239 -0
- data/spec/spec_fast_class_mapping.rb +147 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/spec_serializer.rb +267 -0
- 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
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
|
+
}
|