class Class alias :rtm_old_const_missing :const_missing def const_missing(const_name) puts "Constant missing: #{const_name.inspect}" rtm_old_const_missing(const_name) end end class Array alias :rtm_old_method_missing :method_missing def method_missing(method_name, *args) if size > 0 && first.respond_to?(method_name) && (![:__getobj__, :__setobj__].include?(method_name)) inject([]) {|all,single| all << single.send(method_name, *args)} else rtm_old_method_missing(method_name, *args) end end alias :rtm_old_respond_to? :respond_to? def respond_to?(method_name, *args) resp = rtm_old_respond_to?(method_name, *args) return resp if resp # i.e. if true # check for special calls we don't want to pass return false if [:__getobj__, :__setobj__].include?(method_name) # ... and ask first child otherwise @obj && @obj.size > 0 && first.respond_to?(method_name, *args) end def compact_empty compact.reject{|e| e.empty?} end end class ActiveRecord::Associations::AssociationProxy # this is needed to prevent the associations from being instantly loaded alias :rtm_old_respond_to? :respond_to? def respond_to?(method_name, *args) return false if method_name == :__getobj__ rtm_old_respond_to?(method_name, *args) end end # ActiveRecord implementation module RTM::AR require 'uri' require 'rtm/backend/active_record/tmdm' class TMDelegator def initialize(obj) @obj = obj end def __getobj__ @obj end def __setobj__(obj) @obj = obj end def self.delegate(sym, options={}) to = options[:to] || sym module_eval(<<-EOS, "(__TMDELEGATOR__)", 1) def #{sym}(*args, &block) __getobj__.send(:#{to}, *args, &block) end EOS if options[:rw] module_eval(<<-EOS, "(__TMDELEGATOR2__)", 1) def #{sym}=(*args, &block) __getobj__.send(:#{to}=, *args, &block) #{options[:save] ? "__getobj__.save" : "" } end EOS end if options[:aka] options[:aka] = [options[:aka]] unless options[:aka].respond_to? :each options[:aka].each do |aka| module_eval(<<-EOS, "(__TMDELEGATOR3__)", 1) alias :#{aka} :#{sym} EOS if options[:rw] module_eval(<<-EOS, "(__TMDELEGATOR3__)", 1) alias :#{aka}= :#{sym}= EOS end end end end def self.class_delegate(sym, options={}) module_eval(<<-EOS, "(__TMDELEGATOR__)", 1) class << self def #{sym}(*args, &block) __getobj__.send(:#{sym}, *args, &block) end end EOS if options[:aka] options[:aka] = [options[:aka]] unless options[:aka].respond_to? :each options[:aka].each do |aka| module_eval(<<-EOS, "(__TMDELEGATOR2__)", 1) class << self alias :#{aka} :#{sym} end EOS end end end def self.parent(sym, options={}) module_eval(<<-EOS, "(__TMDELEGATOR__)", 1) def #{sym} TopicMapConstruct.wrap(__getobj__.send(:#{sym})) end EOS aka_property(sym, [:parent, :p]) end def self.property_set(prop, options={}) # puts "In #{self.name.to_s.ljust(20)} we have type #{options[:type].to_s.ljust(20)} for property #{prop}" if options[:wrap] #puts "#{self}: #{options[:type]}" module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY_SET_W__)", 1) def #{prop}(*args) if args.size == 0 # fetch normal #{options[:type]}s.wrap(__getobj__.#{prop}, self, :#{options[:type]}) else # fetch with filter #puts args.inspect # TODO enhance/fix condition transform code a = {}.merge(args.first) if a[:type] && !a[:ttype] a[:ttype] = a[:type] a.delete(:type) end a.each do |k,v| if v.respond_to?(:__getobj__) a[(k.to_s + "_id").to_sym] = v.id end end #{options[:type]}s.wrap(__getobj__.#{prop}.find(:all, :conditions=> a), self, :#{options[:type]}) end end EOS else module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY_SET_NW__)", 1) def #{prop} __getobj__.#{prop} end EOS end if options[:create] # some :create_args: # # create_locator reference:string # [ :name => :reference, :type => :String] # # create_topic_name value:String, scope:Collection # create_topic_name value:String, type: Topic, scope:Collection # [ {:name => :value, :type => :String}, {:name => :type, :type => :Topic, :optional => true}, {:name => :scope, :type => :Collection} ] # # create_occurrence value:String, type: Topic, scope:Collection # create_occurrence resource: Locator, type: Topic, scope:Collection # [ {:name => :value, :type => [:String, :Locator]}, {:name => :type, :type => :Topic}, {:name => :scope, :type => :Collection} ] # # create_association_role player:topic, type: topic # [ {:name => :player, :type => :Topic}, {:name => :type, :type => :Topic}] # # create_variant value:string, :scope:Collection # create_variant resource:locator, :scope:Collection # [ {:name => :value, :type => [:String, :Locator]}, {:name => :scope, :type => :Collection}] module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY_SET_CREATE__)", 1) def create_#{ options[:create]}(*args, &block) arg_defs = #{ (options[:create_args] || nil ).inspect} a = parse_args(args, arg_defs) a = enhance_args_hash(a, arg_defs) if arg_defs a = a.reject{|k,v| v.nil?} # hack to change :type to :ttype for AR backend # puts a.inspect unless a.is_a? Hash if a[:type] && !a[:ttype] a[:ttype] = a[:type] a.delete(:type) end if [:Locator,:ItemIdentifier, :SubjectIdentifier, :SubjectLocator].include?(:#{options[:type]}) unless a[:topic_map] a[:topic_map] = topic_map.__getobj__ end end if block_given? obj = #{options[:type]}.wrap( __getobj__.#{options[:create]}s.new(a) ) yield obj obj.save else obj = #{options[:type]}.wrap( __getobj__.#{options[:create]}s.create(a) ) end obj end EOS if options[:create_aka] aka_property("create_#{options[:create]}", options[:create_aka]) end end if options[:add] module_eval(<<-RUBY, "(__AR_DELEGATOR_PROPERTY_SET_ADD__)", 1) def add_#{prop.to_s.singularize}(*args, &block) #{prop}.add(*args, &block) end RUBY aka_property("add_#{prop.to_s.singularize}", options[:aka].map{|a| "add_#{a}"}) aka_property("add_#{prop.to_s.singularize}", options[:aka].map{|a| "a#{a}"}) end if options[:remove] module_eval(<<-RUBY, "(__AR_DELEGATOR_PROPERTY_SET_REMOVE__)", 1) def remove_#{prop.to_s.singularize}(*args, &block) #{prop}.remove(*args, &block) end RUBY aka_property("remove_#{prop.to_s.singularize}", options[:aka].map{|a| "remove_#{a}"}) aka_property("remove_#{prop.to_s.singularize}", options[:aka].map{|a| "r#{a}"}) end # TODO: aliases for property_set.add as add_property aka_property(prop, options[:aka]) if options[:aka] end private def parse_args(args, arg_defs) a = {} a = args.pop if args.last.is_a? Hash # We are finished if there are no more parameters or we have no arg_def. # Special case: non optional parameters must have been in the Hash, just a hash is also allowed. return a if args.size == 0 || arg_defs == nil # we have some args if args.size == arg_defs.size # all are given return args2hash(a, args,arg_defs) elsif args.size == arg_defs.reject { |d| d[:optional]} return args2hash(a, args, arg_defs.reject {|d| d[:optional]}) end #warn("Functions with more than one optional parameter are not supported. This will only work if the left out ones are the last.") return args2hash(a, args,arg_defs) end def args2hash(a, args, arg_defs) arg_defs.zip(args) do |argd,arg| a[argd[:name]] = arg end return a end def enhance_args_hash(a, arg_defs) #puts "enhance_args: #{a.inspect} ---\n#{arg_defs.inspect}" return a if a.empty? arg_defs.each do |argd| #puts "enhancing #{argd[:name]}, which is a #{a[argd[:name]].class}" if argd[:type] == :Topic if a[argd[:name]].is_a? String a[argd[:name]] = topic_map._topic_by_locator!(a[argd[:name]]) elsif a[argd[:name]].is_a?(RTM::Topic) a[argd[:name]] = a[argd[:name]].__getobj__ end end if argd[:type] == :Locator || (argd[:type].respond_to?(:include?) && argd[:type].include?(:Locator)) a[argd[:name]] = a[argd[:name]].to_s end end a end public def self.property(prop, options={}) module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY__)", 1) def #{prop} #{options[:wrap]? "#{options[:type]}.wrap":""}( __getobj__.#{prop}) end EOS if options[:rw] case options[:type] when :Topic module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY2__)", 1) def #{prop}=(obj) obj = topic_map.topic_by_locator!(obj) unless obj.is_a? #{options[:type]} __getobj__.#{prop} = obj.__getobj__ __getobj__.save end EOS when :TopicMap module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY2__)", 1) def #{prop}=(obj) obj = RTM.create obj.to_s unless obj.is_a? #{options[:type]} __getobj__.#{prop} = obj.__getobj__ __getobj__.save end EOS when :String, :Locator module_eval(<<-EOS, "(__AR_DELEGATOR_PROPERTY2__)", 1) def #{prop}=(obj) __getobj__.#{prop} = obj.to_s __getobj__.save end EOS else raise "Don't know how to do wrapping for #{options[:type]}" end end aka_property(prop, options[:aka]) if options[:aka] end def self.aka_property(prop, aka) aka = [aka].flatten aka.each do |a| #puts "generating alias #{a} for #{prop}" module_eval(<<-EOS, "(__AR_DELEGATOR_AKA_PROPERTY__)", 1) alias :#{a} :#{prop} EOS end end def self.index_property_set(prop, options={}) r = options[:rule] module_eval(<<-EOS, "(__AR_DELEGATOR_INDEX_PROPERTY_SET__)", 1) def direct_#{prop}_roles # prefetch typing topics rtype = @ips_#{prop}_rtype ||= self.topic_map.get("#{r[:role_type]}") atype = @ips_#{prop}_atype ||= self.topic_map.get("#{r[:association_type]}") otype = @ips_#{prop}_otype ||= self.topic_map.get("#{r[:other_role_type]}") # return nothing if any of the typing topics does not exist return [] unless rtype && atype && otype # we do that only here and not earlier because some might me nil rtype = rtype.id atype = atype.id otype = otype.id self.__getobj__.roles.map{|r| r.ttype_id != nil && r.ttype_id == rtype && r.parent.roles.size == #{r[:association_arity]} && r.parent != nil && r.parent.ttype_id == atype && (r2 = r.parent.roles.select{|r2| r2.ttype_id != nil && r2.ttype_id == otype}.first) && r2}.select{|r2| r2}.map{|r2| AssociationRole.wrap(r2)} end def direct_#{prop} direct_#{prop}_roles.map{|r2| r2.player}.uniq end EOS if r[:transitive] module_eval(<<-EOS, "(__AR_DELEGATOR_INDEX_PROPERTY_SET2__)", 1) def #{prop} d = todo = self.direct_#{prop} while todo.size > 0 todo = todo.map{|dt| dt.direct_#{prop}}.flatten.uniq - d d += todo end d #d2 = self.direct_#{prop}.map {|dt| dt.#{prop}}.flatten #(d+d2).uniq end EOS else if r[:infer] module_eval(<<-EOS, "(__AR_DELEGATOR_INDEX_PROPERTY_SET3__)", 1) def #{prop} (self.direct_#{prop} + self.#{r[:infer]}.map{|d2| d2.direct_#{prop}}).flatten.uniq end EOS elsif r[:infer_other] module_eval(<<-EOS, "(__AR_DELEGATOR_INDEX_PROPERTY_SET4__)", 1) def #{prop} d = self.direct_#{prop} (d + d.map{|d2| d2.#{r[:infer_other]}}).flatten.uniq end EOS end end if r[:add] module_eval(<<-EOS, "(__AR_DELEGATOR_INDEX_PROPERTY_SET5__)", 1) def add_#{r[:add]}(t) a = self.topic_map.create_association("#{r[:association_type]}") a.create_role self, "#{r[:role_type]}" a.create_role t, "#{r[:other_role_type]}" a # TODO add_x in index_property_set needs to trigger reload of the topics somewhere end def remove_#{r[:add]}(t) direct_#{prop}_roles.select{|r| r.player == t}.each{|r| r.parent.remove} # TODO remove_x in index_property_set needs to trigger reload of the topics somewhere end EOS end end def self.equality(eqfields, options={}) field_condition = eqfields.map {|f| "self.#{f} == o.#{f}" }.join(" && ") module_eval(<<-EOS, "(__AR_EQUALITY__)", 1) def ==(o) return false unless o return true if #{field_condition} false end EOS end def eql?(o) return false unless o return true if self.class == o.class && self.id == o.id false end def hash return self.id.hash end def self.wrapper_cache module_eval(<<-EOS, "(__AR_WRAPPER_CACHE__)", 1) def self.wrap(obj) return nil unless obj raise "Double wrapping" if obj.respond_to?(:__getobj__) t = self.wrapped(obj) if t t.__setobj__(obj) return t end self.new(obj) end def self.wrapped(unwrapped_obj) @@wrapped ||= {} return @@wrapped[unwrapped_obj.id] if unwrapped_obj.respond_to? :id @@wrapped[unwrapped_obj] end def self.reset_wrapped @@wrapped = {} end def initialize(*args) super @@wrapped ||= {} @@wrapped[self.id]=self end EOS end alias :i :id delegate :reload end class TMSetDelegator < TMDelegator include Enumerable # attr_reader :content_class_name def initialize(obj,parent,type) @obj = obj @parent = parent @type = type end # This class method wraps a Set completely while the instance method wraps one single contained object def self.wrap(obj, parent=nil, type=nil) return nil unless obj raise "Double wrapping" if obj.respond_to?(:__getobj__) self.new(obj, parent, type) end def add(obj) return unless obj old = @obj.detect { |x| x == obj } # can't that be done easier? if old old.merge obj else if obj.respond_to? :__getobj__ @obj << obj.__getobj__ else case @type when :Topic @obj << @parent.topic_map.get!(obj).__getobj__ @parent.__getobj__.reload when :ItemIdentifier if @parent.class.name.to_s =~ /::Topic$/ tbi = @parent.topic_map._item_identifier(obj) if tbi && tmc=tbi.topic_map_construct if tmc.class.name =~ /::Topic$/ return @parent if tmc == @parent.__getobj__ result = Topic.wrap(tmc).merge @parent return result else raise "Duplicate Item Identifier" end end tbsi = @parent.topic_map._subject_identifier(obj.to_s) if tbsi if t=tbsi.topic return @parent if t == @parent.__getobj__ result = Topic.wrap(t).merge @parent # after merging, we still need to add the II result.__getobj__.item_identifiers.create(:reference => obj.to_s, :topic_map_id => @parent.topic_map.__getobj__.id) return result end end end result = @obj << @parent.topic_map._item_identifier!(obj.to_s) return result when :SubjectIdentifier # check for existing item identifier tbi = @parent.topic_map._item_identifier(obj) if tbi && tmc=tbi.topic_map_construct if tmc.class.name =~ /::Topic$/ return @parent if tmc == @parent.__getobj__ result = Topic.wrap(tmc).merge @parent # after merging, we still need to add the SI result.__getobj__.subject_identifiers.create(:reference => obj.to_s, :topic_map_id => @parent.topic_map.__getobj__.id) return result else warn("This subject identifier IRI already belongs to another topic map construct (not a topic)") end end # check for existing subject identifier tbsi = @parent.topic_map._subject_identifier(obj.to_s) if tbsi if true && t=tbsi.topic #the single = is intentional, the "true &&" just makes netbeans not raise a warning return @parent if t == @parent.__getobj__ result = Topic.wrap(t).merge @parent return result end end @obj.create(:reference => obj.to_s, :topic_map_id => @parent.topic_map.__getobj__.id) when :SubjectLocator tbsl = @parent.topic_map._subject_locator(obj.to_s) if tbsl if true && t=tbsl.topic #the single = is intentional, the "true &&" just makes netbeans not raise a warning return @parent if t == @parent.__getobj__ result = Topic.wrap(t).merge @parent return result end end @obj.create(:reference => obj.to_s, :topic_map_id => @parent.topic_map.__getobj__.id) end end end end alias :<< :add def add_all(objs) return unless objs objs.each {|obj| add(obj)} true end def each(&b) @obj.each { |e| yield wrap(e)} end def size @obj.size end alias :length :size def empty? @obj.empty? end def delete(obj) obj = obj.__getobj__ if obj.respond_to? :__getobj__ case @type when :ItemIdentifier, :SubjectIdentifier, :SubjectLocator obj = @obj.find_by_reference(@parent.topic_map.resolve(obj.to_s)) if obj.is_a? String end @obj.delete(obj) # item_identifiers: remove also from topicMap #removed_event obj if respond_to? :removed_event end alias :remove :delete def include?(obj) return @obj.include?(obj) #@obj.each { |e| return true if e == obj } # T#ODO support for get #false end def first wrap(@obj.entries.first) end def last wrap(@obj.entries.last) end def to_s "[#{@obj.entries.map { |e| wrap(e).to_s }.join(", ") }]" end def [](i) wrap(@obj[i]) end def content_class # @content_class ||= RTM.const_get(@content_class_name) @content_class ||= RTM.const_get("#{@content_class_name}MemImpl") end def find(*args) res = @obj.find(*args) if res.respond_to? :each TopicMapConstructs.wrap(res) else TopicMapConstruct.wrap(res) end end def &(other) @obj.to_a & other.to_a end alias :old_method_missing :method_missing def method_missing(method_name, *args) if @obj.size > 0 && first.respond_to?(method_name) && (![:__getobj__, :__setobj__].include?(method_name)) a = [] inject(a) {|all,single| all << single.send(method_name, *args)} a else old_method_missing(method_name, *args) end end alias :old_respond_to? :respond_to? def respond_to?(method_name) resp = old_respond_to?(method_name) return resp if resp # i.e. if true return false if [:__getobj__, :__setobj__].include?(method_name) # ... and ask first child otherwise @obj.size > 0 && first.respond_to?(method_name) end # TMSetDelegator#to_a doesn't help as thought, but maybe we come back to that l8r... #def to_a # @obj.map {|o| wrap(o)} #end end class TopicMapConstruct < TMDelegator include RTM::TopicMapConstruct def self.abstract_class? self == TopicMapConstruct end property_set :item_identifiers, :aka => [:ii, :iid, :source_locators], :type => :ItemIdentifier, :wrap => true, #:create => :item_identifier, :create_aka => :cii :add => true, :remove => true delegate :remove, :to => :destroy delegate :id # property_parent :parent # mmh.. property :topic_map, :rw => true, :type => :TopicMap, :wrap => true #class_delegate :create def self.wrap(obj) return nil unless obj raise "Double wrapping" if obj.respond_to?(:__getobj__) case obj.class.name when "RTM::AR::TMDM::Topic" Topic.wrap(obj) when "RTM::AR::TMDM::Variant" Variant.wrap(obj) when "RTM::AR::TMDM::TopicName" TopicName.wrap(obj) when "RTM::AR::TMDM::Occurrence" Occurrence.wrap(obj) when "RTM::AR::TMDM::Association" Association.wrap(obj) when "RTM::AR::TMDM::AssociationRole" AssociationRole.wrap(obj) when "RTM::AR::TMDM::TopicMap" TopicMap.wrap(obj) else raise "Can't wrap object. Class for wrapping #{obj.class} unknown (object: #{obj})" end end def self.find(*args) res = RTM::AR::TMDM.const_get(name.split("::").last).find(*args) if res.respond_to? :each TopicMapConstructs.wrap(res) else TopicMapConstruct.wrap(res) end end end class Reifiable < TopicMapConstruct include RTM::Reifiable def self.abstract_class? self == Reifiable end property :reifier, :type => :Topic, :rw => :true, :wrap => true end class TopicMap < Reifiable include RTM::TopicMap wrapper_cache property_set :topics, :aka => :t, :type => :Topic, :wrap => true, :create => :topic, :create_aka => :ct property_set :associations, :aka => [:a, :assocs], :type => :Association, :wrap => true, :create => :association, :create_aka => :ca, :create_args => [{:name => :type, :type => :Topic}] delegate :base_locator property_set :association_types, :aka => :at, :type => :Topic, :wrap => true property_set :association_role_types, :aka => [:role_types,:art,:rt], :type => :Topic, :wrap => true property_set :topic_name_types, :aka => [:name_types,:tnt,:nt], :type => :Topic, :wrap => true property_set :occurrence_types, :aka => :ot, :type => :Topic, :wrap => true property_set :topic_names, :aka => [:names,:n], :type => :TopicName, :wrap => :true property_set :occurrences, :aka => :o, :type => :Occurrence, :wrap => :true property_set :association_roles, :aka => [:roles, :r], :type => :AssociationRole, :wrap => :true def types topics.select{|t| t.instances.size > 0} end alias :topic_types :types def fast_types end # This fetches all topics who have no topic-instances (but they might be types for associations etc.). # See indivduals def non_types topics.select{|t| t.instances.size == 0} end # This fetches all topics which are not type for something else (including topics, associations etc.). # See non_types def individuals non_types.select{|t| t.associations_typed.size==0 && t.roles_typed.size==0 && t.names_typed.size==0 && t.occurrences_typed.size==0} end def instances topics.select{|t| t.types.size > 0} end def non_instances topics.reject{|t| t.types.size > 0} end def internal_occurrences occurrences.select{|o| o.datatype != RTM::PSI[:IRI]} end def external_occurrences occurrences.select{|o| o.datatype == RTM::PSI[:IRI]} end def self.create(base_locator, params={}) tm = self.wrap(TMDM::TopicMap.find_or_create_by_base_locator(base_locator)) yield tm if block_given? tm end def self.topic_maps TopicMaps.wrap(TMDM::TopicMap.find(:all)) end # Resolves an IRI or fragment relative to the base_locator of this topic_map or an optional given alternative_base_locator # # Absolute IRIs are taken as is. # # Relative IRIs: # If the base_locator is a directory (ends with "/") it is appended like a file, i.e. directly # If the base_locator is a file it is appended as a fragment (with # in between) def resolve(obj,alternative_base_locator=nil) uri = obj.to_s # TODO uri = URI.decode(obj.to_s) # this InvalidURIError somethimes :( begin uri_uri = URI.parse(uri) rescue URI::InvalidComponentError => ice warn "Catched an URI::InvalidComponentError for URI: #{uri}, message was \"#{ice.message}\"\n" + "Will continue using the UNRESOLVED IRI, claiming it is absolute." return uri end if uri_uri.absolute? return uri_uri.to_s end uri = uri[1..-1] if uri[0] == "#"[0] bl = alternative_base_locator || base_locator if bl[-1,1] == "/" return bl + uri else return bl + "#" + uri end end #private def _item_identifier(iid) __getobj__.locators.find_by_reference(resolve(iid)) # doesn't work :( --> , :include => [ :topic_map_construct ]) end def _item_identifier!(iid) __getobj__.locators.find_or_create_by_reference(resolve(iid)) # doesn't work :( --> , :include => [ :topic_map_construct ]) end def _subject_identifier(iid) __getobj__.subject_identifiers.find_by_reference(iid, :include => :topic) end def _subject_identifier!(iid) __getobj__.subject_identifiers.find_or_create_by_reference(iid, :include => :topic) end def _subject_locator(iid) __getobj__.subject_locators.find_by_reference(iid, :include => :topic) end def _subject_locator!(iid) __getobj__.subject_locators.find_or_create_by_reference(iid, :include => :topic) end # internal helper for by_item_identifier, doesn't wrap result def _by_item_identifier(iid) ii = __getobj__.locators.find_by_reference(resolve(iid)) #, :include => [ :topic_map_construct ]) return ii.topic_map_construct if ii nil end def _topic_by_item_identifier(iid) t = _by_item_identifier(iid) return nil unless t return t if t.class.name =~ /::Topic$/ # only return something if the thing is a topic nil end # internal helper for topic_by_item_identifier!, doesn't wrap result def _topic_by_item_identifier!(iid) ii = __getobj__.locators.find_or_create_by_reference(resolve(iid))#, :include => [ :topic_map_construct ]) return ii.topic_map_construct if ii.topic_map_construct && ii.topic_map_construct_type =~ /::Topic$/ top2 = _topic_by_subject_identifier(resolve(iid)) if top2 ii.topic_map_construct = top2 else ii.topic_map_construct = create_topic.__getobj__ end ii.save ii.topic_map_construct end # internal helper for topic_by_subject_identifier, doesn't wrap result def _topic_by_subject_identifier(sid) si = __getobj__.subject_identifiers.find_by_reference(sid, :include => [ :topic ]) return si.topic if si nil end # internal helper for topic_by_subject_identifier!, doesn't wrap result def _topic_by_subject_identifier!(sid) si = __getobj__.subject_identifiers.find_or_create_by_reference(sid, :include => [ :topic ]) return si.topic if si.topic begin top2 = _by_item_identifier(sid) rescue ActiveRecord::RecordNotFound => rnf si.topic = create_topic.__getobj__ else if top2 && top2.respond_to?(:subject_identifiers) si.topic = top2 else si.topic = create_topic.__getobj__ end end si.save si.topic end # internal helper for topic_by_subject_locator, doesn't wrap result def _topic_by_subject_locator(slo) sl = __getobj__.subject_locators.find_by_reference(RTM::LocatorHelpers.slo2iri(slo)) #, :include => [ :topic ]) return sl.topic if sl nil end # internal helper for topic_by_subject_locator!, doesn't wrap result def _topic_by_subject_locator!(slo) sl = __getobj__.subject_locators.find_or_create_by_reference(RTM::LocatorHelpers.slo2iri(slo), :include => [ :topic ]) return sl.topic if sl.topic sl.topic = create_topic.__getobj__ sl.save sl.topic end def _topic_by_locator(iri) raise "Locator may not be nil" unless iri if RTM::LocatorHelpers.is_a_slo?(iri) _topic_by_subject_locator(iri) elsif URI.parse(iri).absolute? _topic_by_subject_identifier(iri) else _topic_by_item_identifier(iri) end end def _topic_by_locator!(iri) raise "Locator may not be nil" unless iri if RTM::LocatorHelpers.is_a_slo?(iri) _topic_by_subject_locator!(iri) elsif URI.parse(iri).absolute? _topic_by_subject_identifier!(iri) else _topic_by_item_identifier!(iri) end end public # returns an item identifier from the topic_map if it exists def item_identifier(iid) ItemIdentifier.wrap(_item_identifier(iid)) end # returns an item identififier from the topic_map or creates it if it doesn't exist def item_identifier!(iid) ItemIdentifier.wrap(_item_identifier!(iid)) end alias :create_locator :item_identifier! # Returns a topic map construct by it's item identifier or nil if not found. # It's the equivalent to TMAPI's TopicMapObjectIndex.getTopicMapObjectBySourceLocator def by_item_identifier(iid) TopicMapConstruct.wrap(_by_item_identifier(iid)) end # Returns a topic by it's item identifier. The topic will be created if not found. def topic_by_item_identifier!(iid) Topic.wrap(_topic_by_item_identifier!(iid)) end # Returns a topic by it's subject identifier or nil if not found. # It's the equivalent to TMAPI's TopicsIndex.getTopicBySubjectIdentifier. def topic_by_subject_identifier(sid) Topic.wrap(_topic_by_subject_identifier(sid)) end # returns a topic by it's subject identifier. The topic will be created if not found. def topic_by_subject_identifier!(sid) Topic.wrap(_topic_by_subject_identifier!(sid)) end # Returns a topic by it's subject locator or nil if not found. # It's the equivalent to TMAPI's TopicsIndex.getTopicBySubjectLocator def topic_by_subject_locator(slo) Topic.wrap(_topic_by_subject_locator(slo)) end # returns a topic by it's subject locator. The topic will be created if not found. def topic_by_subject_locator!(slo) Topic.wrap(_topic_by_subject_locator!(slo)) end # Gets a topic from this topic map using its (relative) item identifier, # its (absolute) subject identifier or its (absolute and by "=" prepended) subject locator) # # Returns nil if the Topic does not exist. # def topic_by_locator(iri) return iri if iri.is_a? Topic # @tblc ||= {} # t = @tblc[iri] # return t if t t = Topic.wrap(_topic_by_locator(iri)) # @tblc[iri] = t if t # t end alias :get :topic_by_locator # Gets a topic from this topic map using its (relative) item identifier, # its (absolute) subject identifier or its (absolute and by "=" prepended) subject locator) # # If there is no topic with this item identifier, subject identifier or subject locator, it is created. # def topic_by_locator!(iri) return iri if iri.is_a? Topic # @tblc ||= {} # t = @tblc[iri] # return t if t t = Topic.wrap(_topic_by_locator!(iri)) # @tblc[iri] = t # t end alias :get! :topic_by_locator! # I am not sure if this is at all correct. TMDM doesn't say a word about it # and the approach to compare topic maps is CXTM. I chose this because we # should (at least at the time of writing) have only one topic with a given # base locator in RTM. If you don't like it, drop me a mail and explain why # and propose something better. equality [:base_locator] end class Topic < TopicMapConstruct include RTM::Topic wrapper_cache parent :topic_map property_set :subject_identifiers, :aka => [:si,:sid], :type => :SubjectIdentifier, :wrap => true, #:create => :subject_identifier, :create_aka => [:csi,:csid], #:create_args => [{:name => :reference, :type => :String}] :add => true, :remove => true property_set :subject_locators, :aka => [:sl,:slo], :type => :SubjectLocator, :wrap => true, #:create => :subject_locator, :create_aka => [:csl, :cslo], #:create_args => [{:name => :reference, :type => :String}] :add => true, :remove => true property :reified, :computable => true, :type => :Reifiable, :wrap => true property_set :topic_names, :aka => [:n, :names], :type => :TopicName, :wrap => true, :create => :topic_name, :create_aka => [:create_name, :cn], :create_args => [ {:name => :value, :type => :String}, {:name => :type, :type => :Topic, :optional => true}, {:name => :scope, :type => :Collection} ] property_set :occurrences, :aka => :o, :type => :Occurrence, :wrap => true, :create => :occurrence, :create_aka => :co, :create_args => [ {:name => :value, :type => [:String, :Locator]}, {:name => :type, :type => :Topic}, {:name => :scope, :type => :Collection} ] def internal_occurrences occurrences.select{|o| o.datatype != RTM::PSI[:IRI]} end def external_occurrences occurrences.select{|o| o.datatype == RTM::PSI[:IRI]} end property_set :roles, :aka => [:r, :roles_played, :association_roles], :computable => true, :type => :AssociationRole, :wrap => true property_set :associations, :aka => [:a, :associations_played], :type => :Association, :wrap => true property_set :scoped_objects, :type => :TopicMapConstruct, :wrap => true property_set :scoped_associations, :type => :Association, :wrap => true property_set :scoped_topic_names, :aka => :scoped_names, :type => :TopicName, :wrap => true property_set :scoped_variants, :type => :Variant, :wrap => true property_set :scoped_occurrences, :type => :Occurrence, :wrap => true property_set :associations_typed, :aka => :at, :type => :Association, :wrap => true property_set :association_roles_typed, :aka => [:roles_typed,:art,:rt], :type => :AssociationRole, :wrap => true property_set :topic_names_typed, :aka => [:names_typed,:tnt,:nt], :type => :TopicName, :wrap => true property_set :occurrences_typed, :aka => :ot, :type => :Occurrence, :wrap => true # maybe these pairs could be declared each with a single statement and a reversible option index_property_set :types, :type => :Topic, :rule => { :transitive => false, :role_type => RTM::PSI[:instance], :association_type => RTM::PSI[:type_instance], :association_arity => 2, :other_role_type => RTM::PSI[:type], :infer_other => :supertypes, :add => :type, } index_property_set :instances, :type => :Topic, :rule => { :transitive => false, :role_type => RTM::PSI[:type], :association_type => RTM::PSI[:type_instance], :association_arity => 2, :other_role_type => RTM::PSI[:instance], :infer => :subtypes, :add => :instance, } index_property_set :supertypes, :type => :Topic, :rule => { :transitive => true, :role_type => RTM::PSI[:subtype], :association_type => RTM::PSI[:supertype_subtype], :association_arity => 2, :other_role_type => RTM::PSI[:supertype], :add => :supertype, } index_property_set :subtypes, :type => :Topic, :rule => { :transitive => true, :role_type => RTM::PSI[:supertype], :association_type => RTM::PSI[:supertype_subtype], :association_arity => 2, :other_role_type => RTM::PSI[:subtype], :add => :subtype, } def [](topicref) topicref =~ /^(-\s*)?(.+)?$/ if $1 nametype = $2 || RTM::PSI[:name_type] names.select{|n| n.type == topic_map.get(nametype)} else raise "No occurrence type given" unless $2 and !$2.empty? occurrences.select{|o| o.type == topic_map.get($2)} end end def []=(topicref, value) topicref =~ /^(-\s*)?(.+)?$/ if $1 nametype = $2 || topic_map.get!(RTM::PSI[:name_type]) n = create_name(value, nametype) n else raise "No occurrence type given" unless $2 o = create_occurrence(value, $2) o end end # class:Topic equality, http://www.isotopicmaps.org/sam/sam-model/#d0e1029 # # This method is all crap. These ActiveRecord Arrays can't be intersected, # so I map the identifiers to their reference which seems long winded. # Still I find it better than using their ID in the long. # def ==(o) return false unless o # Two topic items are equal if they have: # * at least one equal string in their [subject identifiers] properties, # -> test if intersection are > 0 my_si = self.subject_identifiers.map{|si| si.reference} ot_si = o.subject_identifiers.map{|si| si.reference} return true if ( my_si & ot_si).size > 0 # * at least one equal string in their [item identifiers] properties, my_ii = self.item_identifiers.map{|ii| ii.reference} ot_ii = o.item_identifiers.map{|ii| ii.reference} return true if (my_ii & ot_ii).size > 0 # * at least one equal string in their [subject locators] properties, my_sl = self.subject_locators.map{|sl| sl.reference} ot_sl = o.subject_locators.map{|sl| sl.reference} return true if (my_sl & ot_sl).size > 0 # * an equal string in the [subject identifiers] property of the one topic item and the [item identifiers] property of the other, or return true if (my_si & ot_ii).size > 0 return true if (my_ii & ot_si).size > 0 # * the same information item in their [reified] properties. return true if self.reified != nil && o.reified != nil && (self.reified == o.reified) # ... otherwise false end end class Association < Reifiable include RTM::Association wrapper_cache parent :topic_map property :type, :aka => [:ty,:t], :rw => true, :type => :Topic, :wrap => true property_set :scope, :type => :Topic, :wrap => true property_set :roles, :aka => [:r, :association_roles], :type => :AssociationRole, :wrap => true, :create => :role, :create_aka => [:cr, :create_association_role], :create_args => [ {:name => :player, :type => :Topic}, {:name => :type, :type => :Topic}] property_set :role_players, :aka => [:players, :rp, :pl], :type => :Topic, :wrap => true property_set :role_types, :aka => [:types, :rt], :type => :Topic, :wrap => true equality [:scope, :type, :roles] end class AssociationRole < Reifiable include RTM::AssociationRole wrapper_cache parent :association property :player, :aka => [:pl,:topic], :rw => true, :type => :Topic, :wrap => true property :type, :aka => [:ty,:t], :rw => true, :type => :Topic, :wrap => true def counterparts self.parent.roles.reject{|r| r.id==self.id} end def counterplayers self.counterparts.map{|r| r.player} end def counterpart n = self.parent.roles.size raise "Association must be unary or binary to use counterpart method. Please use counterparts for n-ary associations." if n > 2 return nil if n == 1 self.parent.roles.reject{|r| r.id==self.id}.first end def counterplayer n = self.parent.roles.size raise "Association must be unary or binary to use counterplayer method. Please use counterplayers for n-ary associations." if n > 2 return nil if n == 1 self.parent.roles.reject{|r| r.id==self.id}.first.player end def peers self.counterpart.player.roles.select{|r| r.type == cp.type}.counterpart end def peerplayers self.peers.map{|r| r.player} end equality [:type, :player, :parent] end class TopicName < Reifiable include RTM::TopicName wrapper_cache parent :topic property :type, :aka => [:ty,:t], :rw => true, :type => :Topic, :wrap => true property_set :scope, :type => :Topic, :wrap => true property_set :variants, :aka => :v, :type => :Variant, :wrap => true, :create => :variant, :create_aka => :cv, :create_args => [ {:name => :value, :type => [:String, :Locator]}, {:name => :scope, :type => :Collection}] property :value, :rw => true, :type => :String equality [:value, :type, :scope, :parent] end class Occurrence < Reifiable include RTM::Occurrence wrapper_cache parent :topic property :value, :rw => true, :type => :String property :datatype, :rw => true, :type => :String property_set :scope, :type => :Topic, :wrap => true property :type, :aka => [:ty,:t], :rw => true, :type => :Topic, :wrap => true equality [:value, :datatype, :scope, :type, :parent] end class Variant < Reifiable include RTM::Variant wrapper_cache parent :topic_name, :aka => :name property :value, :rw => true, :type => :String property :datatype, :rw => true, :type => :Locator #, :wrap => true property_set :scope, :type => :Topic, :wrap => true equality [:value, :datatype, :scope, :parent] end class Locator < TMDelegator include RTM::Locator delegate :+ delegate :- #delegate :== delegate :id delegate :get delegate :hash delegate :resolve_relative delegate :to_s, :to => :reference delegate :to_uri delegate :uri delegate :reference, :rw => true, :save => true alias :value :reference alias :value= :reference= equality [:reference] def self.wrap(obj) return nil unless obj raise "Double wrapping" if obj.respond_to?(:__getobj__) case obj.class.name when "RTM::AR::TMDM::ItemIdentifier" ItemIdentifier.wrap(obj) when "RTM::AR::TMDM::SubjectIdentifier" SubjectIdentifier.wrap(obj) when "RTM::AR::TMDM::SubjectLocator" SubjectLocator.wrap(obj) else raise "Can't wrap object. Class for wrapping #{obj.class} unknown (object: #{obj})" end end end class ItemIdentifier < Locator include RTM::ItemIdentifier wrapper_cache property :topic_map_construct, :type => :TopicMapConstruct, :wrap => true end class SubjectIdentifier < Locator include RTM::SubjectIdentifier wrapper_cache property :topic, :type => :Topic, :wrap => true end class SubjectLocator < Locator include RTM::SubjectLocator wrapper_cache property :topic, :type => :Topic, :wrap => true end class Topics < TMSetDelegator def wrap(obj) Topic.wrap(obj) end end class TopicMapConstructs < TMSetDelegator def wrap(obj) TopicMapConstruct.wrap(obj) end end class Associations < TMSetDelegator def wrap(obj) Association.wrap(obj) end end class TopicNames < TMSetDelegator def wrap(obj) TopicName.wrap(obj) end end class Occurrences < TMSetDelegator def wrap(obj) Occurrence.wrap(obj) end end class AssociationRoles < TMSetDelegator def wrap(obj) AssociationRole.wrap(obj) end end class Reifiables < TMSetDelegator def wrap(obj) TopicMapConstruct.wrap(obj) end end class Variants < TMSetDelegator def wrap(obj) Variant.wrap(obj) end end class TopicMaps < TMSetDelegator def [](obj) # support for getting topic_map by base_locator if obj.is_a? String # return wrap(__getobj__.find { |tm| tm.base_locator == obj }) return wrap(TMDM::TopicMap.find_by_base_locator(obj)) end # normal index retrieval super end def wrap(obj) TopicMap.wrap(obj) end end class Locators < TMSetDelegator def [](obj) # support for getting locators by reference if obj.is_a? String return wrap(__getobj__.find { |tm| tm.reference == obj }) end # normal index retrieval super end def wrap(obj) return nil unless obj return obj if obj.respond_to? :__getobj__ case obj.class.name when "RTM::AR::TMDM::ItemIdentifier" ItemIdentifier.wrap(obj) when "RTM::AR::TMDM::SubjectIdentifier" SubjectIdentifier.wrap(obj) when "RTM::AR::TMDM::SubjectLocator" SubjectLocator.wrap(obj) else Locator.wrap(obj) end end end class ItemIdentifiers < Locators def wrap(obj) ItemIdentifier.wrap(obj) end end class SubjectIdentifiers < Locators def wrap(obj) SubjectIdentifier.wrap(obj) end end class SubjectLocators < Locators def wrap(obj) SubjectLocator.wrap(obj) end end end