diff options
Diffstat (limited to 'lib/soap/property.rb')
-rw-r--r-- | lib/soap/property.rb | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/lib/soap/property.rb b/lib/soap/property.rb new file mode 100644 index 0000000000..84fa876cae --- /dev/null +++ b/lib/soap/property.rb @@ -0,0 +1,232 @@ +# soap/property.rb: SOAP4R - Property implementation. +# Copyright (C) 2003 NAKAMURA, Hiroshi <nahi@ruby-lang.org>. + +# This program is copyrighted free software by NAKAMURA, Hiroshi. You can +# redistribute it and/or modify it under the same terms of Ruby's license; +# either the dual license version in 2003, or any later version. + + +module SOAP + + +class Property + include Enumerable + + def initialize + @store = Hash.new + @hook = Hash.new + @self_hook = Array.new + @locked = false + end + + # name: a Symbol, String or an Array + def [](name) + referent(name_to_a(name)) + end + + # name: a Symbol, String or an Array + # value: an Object + def []=(name, value) + hooks = assign(name_to_a(name), value) + normalized_name = normalize_name(name) + hooks.each do |hook| + hook.call(normalized_name, value) + end + value + end + + # value: an Object + # key is generated by property + def <<(value) + self[generate_new_key] = value + end + + # name: a Symbol, String or an Array. nil means hook to the root. + # hook: block which will be called with 2 args, name and value + def add_hook(name = nil, &hook) + if name.nil? + assign_self_hook(hook) + else + assign_hook(name_to_a(name), hook) + end + end + + def each + @store.each do |key, value| + yield(key, value) + end + end + + def empty? + @store.empty? + end + + def keys + @store.keys + end + + def values + @store.values + end + + def lock(cascade = false) + if cascade + each_key do |key| + key.lock(cascade) + end + end + @locked = true + self + end + + def unlock(cascade = false) + @locked = false + if cascade + each_key do |key| + key.unlock(cascade) + end + end + self + end + + def locked? + @locked + end + +protected + + def referent(ary) + key, rest = location_pair(ary) + if rest.empty? + local_referent(key) + else + deref_key(key).referent(rest) + end + end + + def assign(ary, value) + key, rest = location_pair(ary) + if rest.empty? + local_assign(key, value) + local_hook(key) + else + local_hook(key) + deref_key(key).assign(rest, value) + end + end + + def assign_hook(ary, hook) + key, rest = location_pair(ary) + if rest.empty? + local_assign_hook(key, hook) + else + deref_key(key).assign_hook(rest, hook) + end + end + + def assign_self_hook(hook) + check_lock(nil) + @self_hook << hook + end + +private + + def each_key + self.each do |key, value| + if propkey?(value) + yield(value) + end + end + end + + def deref_key(key) + check_lock(key) + ref = @store[key] ||= self.class.new + unless propkey?(ref) + raise ArgumentError.new("key `#{key}' already defined as a value") + end + ref + end + + def local_referent(key) + check_lock(key) + if propkey?(@store[key]) and @store[key].locked? + raise TypeError.new("cannot split any key from locked property") + end + @store[key] + end + + def local_assign(key, value) + check_lock(key) + if @locked + if propkey?(value) + raise TypeError.new("cannot add any key to locked property") + elsif propkey?(@store[key]) + raise TypeError.new("cannot override any key in locked property") + end + end + @store[key] = value + end + + def local_assign_hook(key, hook) + check_lock(key) + @store[key] ||= nil + (@hook[key] ||= []) << hook + end + + NO_HOOK = [].freeze + def local_hook(key) + @self_hook + (@hook[key] || NO_HOOK) + end + + def check_lock(key) + if @locked and (key.nil? or !@store.key?(key)) + raise TypeError.new("cannot add any key to locked property") + end + end + + def propkey?(value) + value.is_a?(::SOAP::Property) + end + + def name_to_a(name) + case name + when Symbol + [name] + when String + name.split(/\./) + when Array + name + else + raise ArgumentError.new("Unknown name #{name}(#{name.class})") + end + end + + def location_pair(ary) + name, *rest = *ary + key = to_key(name) + return key, rest + end + + def normalize_name(name) + name_to_a(name).collect { |key| to_key(key) }.join('.') + end + + def to_key(name) + name.to_s.downcase.intern + end + + def generate_new_key + if @store.empty? + "0" + else + (key_max + 1).to_s + end + end + + def key_max + (@store.keys.max { |l, r| l.to_s.to_i <=> r.to_s.to_i }).to_s.to_i + end +end + + +end |