module Pione module TupleSpace # tuple type table # @api private TUPLE = Hash.new # TupleType represents tuple's field data type. TupleType has simple and complex form, # the latter is consisted by types or-relation. The method +===+ is used by # matching field data and type. # @example # # create simple type # simple_type = TupleType.new(String) # simple_type === "abc" #=> true # # create complex type # complex_type = TupleType.new(String, Symbol) # complex_type === "abc" #=> true # complex_type === :abc #=> true class TupleType < PioneObject class << self alias :or :new end # Creates a tuple field type. # @param [Array] types # tuple field types def initialize(*types) raise ArgumentError.new(types) unless types.size > 0 @types = types end # Returns true if the type is simple. # @return [Boolean] # true if the type is simple, or false def simple? @types.size == 1 end # Returns true if the type is complex. # @return [Boolean] # true if the type is complex, or false def complex? not(simple?) end # @api private def ===(other) @types.find {|t| t === other} end end module TupleDefinition # Defines a tuple format and create a class representing it. # @return [void] def define_format(format) raise ScriptError if @format @format = format identifier = format.first set_attr_accessors # check arguments: format is a list of symbols format.each do |name, _| unless Symbol === name raise TupleFormatError.new(name, identifier) end end # forbid to define same identifier and different format if TUPLE.has_key?(identifier) if not(TUPLE[identifier].format == format) raise TupleFormatError.new(format, identifier) else return TUPLE[identifier] end end # make a class and set it in a table TUPLE[identifier] = self end # Deletes a tuple format definition. # @return [void] def delete_format(identifier) if TUPLE.has_key?(identifier) name = TUPLE[identifier].name.split('::').last TUPLE.delete(identifier) remove_const(name) end end # Returns tuple's format. # @return [Array] # tuple's format def format @format end # Returns the identifier. # @return [Symbol] # identifier of the tuple def identifier @format.first end # Returns a respresentation for matching any tuples of same type. # @return [TupleObject] # a query tuple that matches any tuples has the identifier def any new end # Returns domain position of the format. # @return [Integer, nil] # position number of domain field, or nil # @api private def domain_position position_of(:domain) || position_of(:domain_id) end # Return location position of the format. # # @return [Integer or nil] # position number of location field, or nil # @api private def location_position position_of(:location) end private # Sets the tuple format and creates accessor methods. # @param [Array] definition # tuple format # @return [void] def set_attr_accessors @format.each do |key, _| define_method(key) {@data[key]} define_method("%s=" % key) {|val| @data[key] = val} end end # @api private def position_of(name) @format.each_with_index do |key, i| key = key.kind_of?(Array) ? key.first : key return i if key == name end return nil end end # TupleObject is a superclass for all tuple classes. class BasicTuple < PioneObject def self.inherited(klass) klass.extend TupleDefinition end attr_accessor :timestamp # Creates new tuple object. # @param [Hash] data # tuple data def initialize(*data) @data = {} return if data.empty? format = self.class.format format_keys = format.map{|key,_| key} format_table = Hash[*format[1..-1].select{|item| item.kind_of?(Array) }.flatten(1)] if data.first.kind_of?(Hash) _data = data.first _data.keys.each do |key| # key check unless format_keys.include?(key) raise TupleFormatError.new(key, format.first) end # type check if _data[key] && not(format_table[key].nil?) unless format_table[key] === _data[key] raise TupleFormatError.new(_data[key], format.first) end end end @data = _data else # length check unless data.size == format.size - 1 raise TupleFormatError.new(data, format.first) end # type check data.each_with_index do |key, i| if format[i+1].kind_of?(Array) # type specified unless format[i+1][1] === data[i] or data[i].nil? raise TupleFormatError.new(data[i], format.first) end end end @data = Hash[format_keys[1..-1].zip(data)] end end # @api private def ==(other) return false unless self.class == other.class to_tuple_space_form == other.to_tuple_space_form end alias :eql? :"==" # @api private def hash @data.hash end # Returns the identifier. # @return [Symbol] # tuple identifier def identifier self.class.identifier end # Converts the tuple to string form. # @api private def to_s "#<#<#{self.class.name}> #{to_tuple_space_form.to_s}>" end # Convert to plain tuple form. # @return [Array] # tuple data array for Rinda's tuple space def to_tuple_space_form self.class.format[1..-1].map{|key, _| @data[key]}.unshift(identifier) end # Converts the tuple to json form. # @return [String] # json form of the tuple def to_json(*a) @data.merge({"tuple" => self.class.identifier}).to_json(*a) end # Returns the value of the specified position. # @param [Integer] i # field position to get # @return # the value def value(i = 0) @data[i] end # Returns true if the field writable. # @return [Boolean] def writable? self.class.format.map do |symbol| @data.has_key?(symbol) end.unique == [true] end def set(data) self.class.new(@data.merge(data)) end end class << self # Returns a tuple class corresponding to a tuple identifier. # @return [Class] # tuple class def [](identifier) TUPLE[identifier] end # Returns identifiers. # @return [Array] # all tuple identifiers in PIONE system. def identifiers TUPLE.keys end # Return a tuple data object converted from an array. # @return [TupleObject] # tuple object def from_array(ary) raise TupleFormatError.new(ary) unless ary.size > 0 raise TupleFormatError.new(ary) unless ary.respond_to?(:to_a) _ary = ary.to_a identifier = _ary.first raise TupleFormatError.new(identifier) unless TUPLE.has_key?(identifier) args = _ary[1..-1] TUPLE[identifier].new(*args) end end # # handle tuple definition file # YAML.load_file(File.join(File.dirname(__FILE__), "tuple-definition.yml")).each do |class_name, definition| klass = Class.new(BasicTuple) const_set(class_name, klass) format = definition.map do |d| # key is field name, and value is type (value's class name) if d.kind_of?(Hash) [d.keys.first.to_sym, eval(d[d.keys.first])] else d.to_sym end end klass.module_eval {klass.define_format(format)} end # # special tuple extension # class ProcessLogTuple def timestamp=(time) @timestamp = time message.timestamp = time end end class DataTuple attr_accessor :update_criteria attr_accessor :accept_nonexistence end end end