RocketAMF-ouvrages 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +47 -0
- data/Rakefile +59 -0
- data/RocketAMF.gemspec +20 -0
- data/benchmark.rb +74 -0
- data/ext/rocketamf_ext/class_mapping.c +484 -0
- data/ext/rocketamf_ext/constants.h +52 -0
- data/ext/rocketamf_ext/deserializer.c +770 -0
- data/ext/rocketamf_ext/deserializer.h +27 -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/rocketamf.rb +216 -0
- data/lib/rocketamf/class_mapping.rb +237 -0
- data/lib/rocketamf/constants.rb +50 -0
- data/lib/rocketamf/ext.rb +28 -0
- data/lib/rocketamf/extensions.rb +22 -0
- data/lib/rocketamf/pure.rb +24 -0
- data/lib/rocketamf/pure/deserializer.rb +455 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +117 -0
- data/lib/rocketamf/pure/serializer.rb +474 -0
- data/lib/rocketamf/remoting.rb +196 -0
- data/lib/rocketamf/values/messages.rb +214 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/spec/class_mapping_spec.rb +110 -0
- data/spec/deserializer_spec.rb +449 -0
- data/spec/fast_class_mapping_spec.rb +144 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +1 -0
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-time.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +1 -0
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
- data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
- data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
- data/spec/fixtures/objects/amf3-false.bin +1 -0
- data/spec/fixtures/objects/amf3-float.bin +0 -0
- data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/amf3-max.bin +1 -0
- data/spec/fixtures/objects/amf3-min.bin +0 -0
- data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
- data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
- data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
- data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/fixtures/request/acknowledge-response.bin +0 -0
- data/spec/fixtures/request/amf0-error-response.bin +0 -0
- data/spec/fixtures/request/blaze-response.bin +0 -0
- data/spec/fixtures/request/commandMessage.bin +0 -0
- data/spec/fixtures/request/flex-request.bin +0 -0
- data/spec/fixtures/request/multiple-simple-request.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-request.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
- data/spec/messages_spec.rb +39 -0
- data/spec/remoting_spec.rb +196 -0
- data/spec/serializer_spec.rb +503 -0
- data/spec/spec_helper.rb +55 -0
- metadata +167 -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,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
|
data/RocketAMF.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'RocketAMF-ouvrages'
|
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/rubyamf/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
|
data/benchmark.rb
ADDED
@@ -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
|
+
}
|