require 'rocketamf/mapping/mapping_set'
module RocketAMF
# Handles class name mapping between AS and ruby and assists in
# serializing and deserializing data between them. Simply map an AS class to a
# ruby class and when the object is (de)serialized it will end up as the
# appropriate class.
#
# Example:
#
# RocketAMF::CLASS_MAPPER.define do |m|
# m.map as: 'AsClass', ruby: 'RubyClass'
# m.map as: 'vo.User', ruby: 'Model::User'
# end
#
# == Object Population/Serialization
#
# In addition to handling class name mapping, it also provides helper methods
# for populating ruby objects from AMF and extracting properties from ruby objects
# for serialization. Support for hash-like objects and objects using
# attr_accessor for properties is currently built in, but custom classes
# may require subclassing the class mapper to add support.
#
# == Complete Replacement
#
# In some cases, it may be beneficial to replace the default provider of class
# mapping completely. In this case, simply assign your class mapper class to
# RocketAMF::CLASS_MAPPER after loading RocketAMF. Through the magic of
# const_missing, CLASS_MAPPER is only defined after the first
# access by default, so you get no annoying warning messages. Custom class mappers
# must implement the following methods on instances: use_array_collection,
# get_as_class_name, get_ruby_obj, populate_ruby_obj,
# and props_for_serialization. In addition, it should have a class level
# mappings method that returns the mapping set it's using, although its
# not required. If you'd like to see an example of what complete replacement
# offers, check out RubyAMF (http://github.com/rubyamf/rubyamf).
#
# Example:
#
# require 'rubygems'
# require 'rocketamf'
#
# RocketAMF.const_set(CLASS_MAPPER, MyCustomClassMapper)
#
# == C ClassMapper
#
# The C class mapper, RocketAMF::Ext::FastClassMapping, has the same
# public API that RubyAMF::ClassMapping does, but has some additional
# performance optimizations that may interfere with the proper serialization of
# objects. To reduce the cost of processing public methods for every object,
# its implementation of props_for_serialization caches valid properties
# by class, using the class as the hash key for property lookup. This means that
# adding and removing properties from instances while serializing using a given
# class mapper instance will result in the changes not being detected. As such,
# it's not enabled by default. So long as you aren't planning on modifying
# classes during serialization using encode_amf, the faster C class
# mapper should be perfectly safe to use.
#
# Activating the C Class Mapper:
#
# require 'rubygems'
# require 'rocketamf'
#todo:review
# RocketAMF::ClassMapper = RocketAMF::Ext::FastClassMapping
class ClassMapping
class << self
# Global configuration variable for sending Arrays as ArrayCollections.
# Defaults to false.
attr_accessor :use_array_collection
# Returns the mapping set with all the class mappings that is currently
# being used.
def mappings
@mappings ||= MappingSet.new
end
# Define class mappings in the block. Block is passed a MappingSet object
# as the first parameter.
#
# Example:
#
# RocketAMF::CLASS_MAPPER.define do |m|
# m.map as: 'AsClass', ruby: 'RubyClass'
# end
def define(&block) #:yields: mapping_set
yield mappings
end
# Reset all class mappings except the defaults and return
# use_array_collection to false
def reset
@use_array_collection = false
@mappings = nil
end
end
#
# Properties
#
attr_reader :use_array_collection
#
# Methods
#
# Copies configuration from class level configs to populate object
public
def initialize
@mappings = self.class.mappings
@use_array_collection = self.class.use_array_collection === true
end
# Returns the ActionScript class name for the given ruby object. Will also
# take a string containing the ruby class name.
def get_as_class_name(obj)
# Get class name
if obj.is_a?(String)
ruby_class_name = obj
elsif obj.is_a?(Types::TypedHash)
ruby_class_name = obj.type
elsif obj.is_a?(Hash)
return nil
else
ruby_class_name = obj.class.name
end
# Get mapped AS class name
@mappings.get_as_class_name(ruby_class_name)
end
# Instantiates a ruby object using the mapping configuration based on the
# source ActionScript class name. If there is no mapping defined, it returns
# a RocketAMF::Types::TypedHash with the serialized class name.
public
def get_ruby_obj(as_class_name)
result = nil
ruby_class_name = @mappings.get_ruby_class_name(as_class_name)
if ruby_class_name.nil?
# Populate a simple hash, since no mapping
result = Types::TypedHash.new(as_class_name)
else
ruby_class = ruby_class_name.split('::').inject(Kernel) { |scope, const_name| scope.const_get(const_name) }
result = ruby_class.new
end
result
end
# Populates the ruby object using the given properties. props will be hashes with symbols for keys.
public
def populate_ruby_obj(target, props)
# Don't even bother checking if it responds to setter methods if it's a TypedHash
if target.is_a?(Types::TypedHash)
target.merge! props
return target
end
# Some type of object
hash_like = target.respond_to?("[]=")
props.each do |key, value|
if target.respond_to?("#{key}=")
target.send("#{key}=", value)
elsif hash_like
target[key] = value
end
end
target
end
# Extracts all exportable properties from the given ruby object and returns
# them in a hash. If overriding, make sure to return a hash wth string keys
# unless you are only going to be using the native C extensions, as the pure
# ruby serializer performs a sort on the keys to acheive consistent, testable
# results.
public
def props_for_serialization(ruby_obj)
result = {}
# Handle hashes
if ruby_obj.is_a?(Hash)
# Stringify keys to make it easier later on and allow sorting
ruby_obj.each { |k, v| result[k.to_s] = v }
else
# Generic object serializer
@ignored_props ||= Object.new.public_methods
(ruby_obj.public_methods - @ignored_props).each do |method_name|
# Add them to the prop hash if they take no arguments
method_def = ruby_obj.method(method_name)
result[method_name.to_s] = ruby_obj.send(method_name) if method_def.arity == 0
end
end
result
end # props_for_serialization
end #ClassMapping
end #RocketAMF