lib/activefacts/input/orm.rb in activefacts-0.8.10 vs lib/activefacts/input/orm.rb in activefacts-0.8.12

- old
+ new

@@ -129,10 +129,11 @@ } # Everything we build will be indexed here: @by_id = {} + list_subtypes read_entity_types read_value_types read_fact_types read_nested_types read_subtypes @@ -169,60 +170,81 @@ def read_value_types # Now the value types: value_types = [] x_value_types = @x_model.xpath("orm:Objects/orm:ValueType") - #pp x_value_types + @value_type_id_read = {} x_value_types.each{|x| - id = x['id'] - name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip - name = nil if name.size == 0 - next if x['IsImplicitBooleanValue'] + value_types << read_value_type(x) + } + end - cdt = x.xpath('orm:ConceptualDataType')[0] - scale = cdt['Scale'] - scale = scale != "" && scale.to_i - length = cdt['Length'] - length = length != "" && length.to_i - length = nil if length <= 0 - base_type = @x_by_id[cdt['ref']] - type_name = "#{base_type.name}" - type_name.sub!(/^orm:/,'') + def read_value_type x + id = x['id'] + return if @value_type_id_read[id] # Don't read the same value type twice + @value_type_id_read[id] = true - type_name.sub!(/DataType\Z/,'') - type_name = DataTypeMapping[type_name] || type_name - if !length and type_name =~ /\(([0-9]+)\)/ - length = $1.to_i - end - type_name = type_name.sub(/\(([0-9]*)\)/,'') + name = (x['Name'] || "").gsub(/\s+/,' ').gsub(/-/,'_').strip + name = nil if name.size == 0 + cdt = x.xpath('orm:ConceptualDataType')[0] + scale = cdt['Scale'] + scale = scale != "" && scale.to_i + length = cdt['Length'] + length = length != "" && length.to_i + length = nil if length <= 0 + base_type = @x_by_id[cdt['ref']] + type_name = "#{base_type.name}" + type_name.sub!(/^orm:/,'') + + type_name.sub!(/DataType\Z/,'') + type_name = DataTypeMapping[type_name] || type_name + if !length and type_name =~ /\(([0-9]+)\)/ + length = $1.to_i + end + type_name = type_name.sub(/\(([0-9]*)\)/,'') + + subtype_roles = x.xpath("orm:PlayedRoles/orm:SubtypeMetaRole") + if !subtype_roles.empty? + subtype_role_id = subtype_roles[0]['ref'] + subtype_role = @x_by_id[subtype_role_id] + subtyping_fact_roles = subtype_role.parent + supertype_id = subtyping_fact_roles.xpath("orm:SupertypeMetaRole/orm:RolePlayer")[0]['ref'] + x_supertype = @x_by_id[supertype_id] + read_value_type x_supertype unless @value_type_id_read[supertype_id] + supertype = @by_id[supertype_id] + supertype_name = x_supertype['Name'] + raise "Supertype of #{name} is post-defined but recursiving processing failed" unless supertype + raise "Supertype #{supertype_name} of #{name} is not a value type" unless supertype.kind_of? ActiveFacts::Metamodel::ValueType + value_super_type = @constellation.ValueType(@vocabulary, supertype_name) + else # REVISIT: Need to handle standard types better here: value_super_type = type_name != name ? @constellation.ValueType(@vocabulary, type_name) : nil + end - value_types << - @by_id[id] = - vt = @constellation.ValueType(@vocabulary, name) - vt.supertype = value_super_type - vt.length = length if length - vt.scale = scale if scale && scale != 0 - independent = x['IsIndependent'] - vt.is_independent = true if independent && independent == 'true' - personal = x['IsPersonal'] - vt.pronoun = 'personal' if personal && personal == 'true' + @by_id[id] = + vt = @constellation.ValueType(@vocabulary, name) + vt.supertype = value_super_type + vt.length = length if length + vt.scale = scale if scale && scale != 0 + independent = x['IsIndependent'] + vt.is_independent = true if independent && independent == 'true' + personal = x['IsPersonal'] + vt.pronoun = 'personal' if personal && personal == 'true' - x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint") - x_vr.each{|vr| - x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange") - next if x_ranges.size == 0 - vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(:new) - x_ranges.each{|x_range| - v_range = value_range(x_range) - ar = @constellation.AllowedRange(vt.value_constraint, v_range) - } + x_vr = x.xpath("orm:ValueRestriction/orm:ValueConstraint") + x_vr.each{|vr| + x_ranges = vr.xpath("orm:ValueRanges/orm:ValueRange") + next if x_ranges.size == 0 + vt.value_constraint = @by_id[vr['id']] = @constellation.ValueConstraint(:new) + x_ranges.each{|x_range| + v_range = value_range(x_range) + ar = @constellation.AllowedRange(vt.value_constraint, v_range) } } + vt end def value_range(x_range) min = x_range['MinValue'] max = x_range['MaxValue'] @@ -252,21 +274,24 @@ facts << @by_id[id] = fact_type = @constellation.FactType(:new) } end end - def read_subtypes - # Handle the subtype fact types: - facts = [] + def list_subtypes @x_subtypes = @x_model.xpath("orm:Facts/orm:SubtypeFact") if @document.namespaces['xmlns:oialtocdb'] oialtocdb = @document.xpath("ormRoot:ORM2/oialtocdb:MappingCustomization") @x_mappings = oialtocdb.xpath(".//oialtocdb:AssimilationMappings/oialtocdb:AssimilationMapping/oialtocdb:FactType") else @x_mappings = [] end + end + def read_subtypes + # Handle the subtype fact types: + facts = [] + debug :orm, "Reading sub-types" do @x_subtypes.each{|x| id = x['id'] name = (x['Name'] || x['_Name'] || '').gsub(/\s+/,' ').gsub(/-/,'_').strip name = nil if name.size == 0 @@ -285,10 +310,14 @@ throw "For Subtype fact #{name}, the supertype #{supertype_id} was not found" if !supertype throw "For Subtype fact #{name}, the subtype #{subtype_id} was not found" if !subtype debug :orm, "#{subtype.name} is a subtype of #{supertype.name}" + # We already handled ValueType subtyping: + next if subtype.kind_of? ActiveFacts::Metamodel::ValueType or + supertype.kind_of? ActiveFacts::Metamodel::ValueType + inheritance_fact = @constellation.TypeInheritance(subtype, supertype, :fact_type_id => :new) if x["IsPrimary"] == "true" or # Old way x["PreferredIdentificationPath"] == "true" # Newer debug :orm, "#{supertype.name} is primary supertype of #{subtype.name}" inheritance_fact.provides_identification = true @@ -337,23 +366,18 @@ fact_id = x_fact_type['ref'] fact_type = @by_id[fact_id] next if x.xpath("orm:DerivationRule").size > 0 throw "Nested fact #{fact_id} not found" if !fact_type - #if is_implied - # puts "Implied type #{name} (#{id}) nests #{fact_type ? fact_type.fact_type_id : "unknown"}" - # @by_id[id] = fact_type - #else - begin - debug :orm, "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}" - @nested_types << - @by_id[id] = - nested_type = @constellation.EntityType(@vocabulary, name) - independent = x['IsIndependent'] - nested_type.is_independent = true if independent && independent == 'true' && !is_implied - nested_type.fact_type = fact_type - end + debug :orm, "NestedType #{name} is #{id}, nests #{fact_type.fact_type_id}" + @nested_types << + @by_id[id] = + nested_type = @constellation.EntityType(@vocabulary, name) + independent = x['IsIndependent'] + nested_type.is_independent = true if independent && independent == 'true' && !is_implied + nested_type.is_implied_by_objectification = is_implied + nested_type.fact_type = fact_type } end end def complete_nested_types @@ -475,20 +499,28 @@ (0...all_role_refs.size).each{|i| role_ref = all_role_refs[i] role = role_ref.role word = '\b[A-Za-z_][A-Za-z0-9_]+\b' - leading_adjectives_re = "#{word}-(?: +#{word})*" - trailing_adjectives_re = "(?:#{word} +)*-#{word}" + leading_adjectives_re = "#{word}-+(?: +#{word})*" + trailing_adjectives_re = "(?:#{word} +)*-+#{word}" role_with_adjectives_re = %r| ?(#{leading_adjectives_re})? *\{#{i}\} *(#{trailing_adjectives_re})? ?| + # A hyphenated pre-bound reading looks like this: + # <orm:Data>{0} has pre-- bound {1}</orm:Data> + text.gsub!(role_with_adjectives_re) { # REVISIT: Don't want to strip all spaces here any more: #puts "text=#{text.inspect}, la=#{$1.inspect}, ta=#{$2.inspect}" if $1 || $2 - la = ($1||'').gsub(/\s+/,' ').sub(/-/,'').strip - ta = ($2||'').gsub(/\s+/,' ').sub(/-/,'').strip + la = ($1||'').gsub(/\s+/,' ') # Strip duplicate spaces + ta = ($2||'').gsub(/\s+/,' ') + # When we have "aaa-bbb" we want "aaa bbb" + # When we have "aaa- bbb" we want "aaa bbb" + # When we have "aaa-- bbb" we want "aaa-bbb" + la = la.sub(/(-)?- ?/,'\1').strip + ta = ta.sub(/ ?(-)?-/,'\1').strip #puts "Setting leading adj #{la.inspect} from #{text.inspect} for #{role_ref.role.object_type.name}" if la != "" # REVISIT: Dunno what's up here, but removing the "if" test makes this chuck exceptions: role_ref.leading_adjective = la if la != "" role_ref.trailing_adjective = ta if ta != "" @@ -573,11 +605,13 @@ next if x_role.parent.parent.xpath('orm:DerivationRule').size > 0 # Talk about why this wasn't found - this shouldn't happen. if (!x_nests || !implied) #puts "="*60 - puts "Skipping #{why}, #{x_role.name} #{id} not found" + # We skip creating TypeInheritance implied fact types for ValueType inheritance + return nil if x_role.name = 'orm:SubtypeMetaRole' or x_role.name = 'orm:SupertypeMetaRole' + raise "Skipping #{why}, #{x_role.name} #{id} not found" if (x_nests) puts "Role is on #{implied ? "implied " : ""}objectification #{x_object}" puts "which objectifies #{x_fact}" end @@ -793,200 +827,208 @@ # to the respective end-point (constrained object type). # Also, all roles in each sequence constitute a join over a single # object type, which might involve subtyping or objectification joins. # def make_joins(constraint_type, name, role_sequences) - # Get the object types constrained for each position in the role sequences. - # Supertyping joins may be needed to reach them. - end_points = [] # An array of the common supertype for matching role_refs across the sequences - end_joins = [] # An array of booleans indicating whether any role_sequence requires a subtyping join - role_sequences[0].all_role_ref.size.times do |i| - role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}} - if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1 - raise "In #{constraint_type} #{name}, there is a faulty join" - next - end - if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1 - end_point = players[0] - end_joins[i] = false - else - # Can the players be joined using a subtyping join? - common_supertypes = players[1..-1]. - inject(players[0].supertypes_transitive) do |remaining, player| - remaining & player.supertypes_transitive - end - end_point = common_supertypes[0] + begin + # Get the object types constrained for each position in the role sequences. + # Supertyping joins may be needed to reach them. + end_points = [] # An array of the common supertype for matching role_refs across the sequences + end_joins = [] # An array of booleans indicating whether any role_sequence requires a subtyping join + role_sequences[0].all_role_ref.size.times do |i| + role_refs = role_sequences.map{|rs| rs.all_role_ref.detect{|rr| rr.ordinal == i}} + if (fact_types = role_refs.map{|rr| rr.role.fact_type}).uniq.size == 1 + # $stderr.puts(role_sequences.map{|rs| rs.all_role_ref.map{|rr| rr.role.fact_type.describe(rr.role)}}.inspect) + raise "In #{constraint_type} #{name} role sequence #{i}, there is a faulty join involving just 1 fact type: '#{fact_types[0].default_reading}'" + end + if (players = role_refs.map{|rr| rr.role.object_type}).uniq.size == 1 + end_point = players[0] + end_joins[i] = false + else + # Can the players be joined using a subtyping join? + common_supertypes = players[1..-1]. + inject(players[0].supertypes_transitive) do |remaining, player| + remaining & player.supertypes_transitive + end + end_point = common_supertypes[0] - raise "constrained roles of #{constraint_type} constraint #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0 - end_joins[i] = true + raise "constrained roles of #{constraint_type} #{name} are incompatible (#{players.map(&:name)*', '})" if common_supertypes.size == 0 + end_joins[i] = true + end + end_points[i] = end_point end - end_points[i] = end_point - end - # For each role_sequence, find the object type over which the join is implied (nil if no join) - sequence_join_over = [] - if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence. + # For each role_sequence, find the object type over which the join is implied (nil if no join) sequence_join_over = [] - sequence_joined_roles = [] - role_sequences.map do |rs| - join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role}) - sequence_join_over << join_over - sequence_joined_roles << joined_roles + if role_sequences[0].all_role_ref.size > 1 # There are joins within each sequence. + sequence_join_over = [] + sequence_joined_roles = [] + role_sequences.map do |rs| + join_over, joined_roles = *ActiveFacts::Metamodel.join_roles_over(rs.all_role_ref.map{|rr| rr.role}) + sequence_join_over << join_over + sequence_joined_roles << joined_roles + end end - end - # If there are no joins, we can drop out here. - if sequence_join_over.compact.empty? && !end_joins.detect{|e| true} - return - end - - debug :join, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}" - - join = nil - debug :join, "#{constraint_type} join constraint #{name} constrains #{ - end_points.zip(end_joins).map{|(p,j)| p.name+(j ? ' & subtypes':'')}*', ' - }#{ - if role_sequences[0].all_role_ref.size > 1 - ", joined over #{ - sequence_join_over.zip(sequence_joined_roles).map{|o, roles| - (o ? o.name : '(none)') + - (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '') - }*', '}" - else - '' + # If there are no joins, we can drop out here. + if sequence_join_over.compact.empty? && !end_joins.detect{|e| true} + return true end - }" do - # There may be one join per role sequence: - role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles| - # Skip if there's no join here (sequence join nor end-point subset join) - role_refs = role_sequence.all_role_ref_in_order - if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]} - # No sequence join nor end_point join here - next + debug :join, "#{constraint_type} join constraint #{name} over #{role_sequences.map{|rs|rs.describe}*', '}" + + join = nil + debug :join, "#{constraint_type} join constraint #{name} constrains #{ + end_points.zip(end_joins).map{|(p,j)| (p ? p.name : 'NULL')+(j ? ' & subtypes':'')}*', ' + }#{ + if role_sequences[0].all_role_ref.size > 1 + ", joined over #{ + sequence_join_over.zip(sequence_joined_roles).map{|o, roles| + (o ? o.name : '(none)') + + (roles ? " to (#{roles.map{|role| role ? role.fact_type.default_reading : 'null'}*','})" : '') + }*', '}" + else + '' end + }" do - # A RoleSequence for the actual join end-points - replacement_rs = @constellation.RoleSequence(:new) + # There may be one join per role sequence: + role_sequences.zip(sequence_join_over||[], sequence_joined_roles||[]).map do |role_sequence, join_over, joined_roles| + # Skip if there's no join here (sequence join nor end-point subset join) + role_refs = role_sequence.all_role_ref_in_order + if !join_over and !role_refs.detect{|rr| rr.role.object_type != end_points[rr.ordinal]} + # No sequence join nor end_point join here + next + end - join = @constellation.Join(:new) - join_node = nil - join_role = nil - role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i| + # A RoleSequence for the actual join end-points + replacement_rs = @constellation.RoleSequence(:new) - # Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node) + join = @constellation.Join(:new) + join_node = nil + join_role = nil + role_refs.zip(joined_roles||[]).each_with_index do |(role_ref, joined_role), i| - # Create a join node for the actual end-point (supertype of the constrained roles) - end_point = end_points[i] - raise "In #{constraint_type} #{name}, there is a faulty join" unless end_point - debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}" - end_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => end_point) + # Each role_ref is to an object joined via joined_role to join_node (or which will be the join_node) - # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same: - role_node = end_node - end_role = role_ref.role - - # Create subtyping join steps at the end-point, if needed: - projecting_jr = nil - constrained_join_role = nil - if (subtype = role_ref.role.object_type) != end_point - debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do - # There may be more than one supertyping level. Make the steps: - subtyping_steps = subtype_join_steps(join, subtype, end_point) - js = subtyping_steps[0] - constrained_join_role = subtyping_steps[-1].input_join_role - - # Replace the constrained role and node with the supertype ones: - end_node = join.all_join_node.detect{|jn| jn.object_type == end_point } - projecting_jr = js.output_join_role - end_role = js.fact_type.all_role.detect{|r| r.object_type == end_point } - role_node = join.all_join_node.detect{|jn| jn.object_type == role_ref.role.object_type } + # Create a join node for the actual end-point (supertype of the constrained roles) + end_point = end_points[i] + unless end_point + raise "In #{constraint_type} #{name}, there is a faulty or non-translated join" end - end + debug :join, "Join Node #{join.all_join_node.size} is for #{end_point.name}" + end_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => end_point) - raise "Internal error: making illegal reference to join node" if end_role.object_type != end_node.object_type - rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role) - projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role)) - projecting_jr.role_ref = rr # Project this RoleRef + # We're going to rewrite the constraint to constrain the supertype roles, but assume they're the same: + role_node = end_node + end_role = role_ref.role - if join_over - if !join_node # Create the JoinNode when processing the first role - debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}" - join_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => join_over) - end - debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do - rs = @constellation.RoleSequence(:new) - # Detect the fact type over which we're joining (may involve objectification) - raise "Internal error: making illegal reference to join node" if role_ref.role.object_type != role_node.object_type - @constellation.RoleRef(rs, 0, :role => role_ref.role) - role_jr = @constellation.JoinRole(role_node, role_ref.role) - raise "Internal error: making illegal reference to join node" if joined_role.object_type != join_node.object_type - @constellation.RoleRef(rs, 1, :role => joined_role) - join_jr = @constellation.JoinRole(join_node, joined_role) + # Create subtyping join steps at the end-point, if needed: + projecting_jr = nil + constrained_join_role = nil + if (subtype = role_ref.role.object_type) != end_point + debug :join, "Making subtyping join steps from #{subtype.name} to #{end_point.name}" do + # There may be more than one supertyping level. Make the steps: + subtyping_steps = subtype_join_steps(join, subtype, end_point) + js = subtyping_steps[0] + constrained_join_role = subtyping_steps[-1].input_join_role - js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type) - debug :join, "New join step #{js.describe}" + # Replace the constrained role and node with the supertype ones: + end_node = join.all_join_node.detect{|jn| jn.object_type == end_point } + projecting_jr = js.output_join_role + end_role = js.fact_type.all_role.detect{|r| r.object_type == end_point } + role_node = join.all_join_node.detect{|jn| jn.object_type == role_ref.role.object_type } + end end - else - debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}" - if (roles = role_ref.role.fact_type.all_role.to_a).size > 1 - # Here we have an end join (step already created) but no sequence join - if join_node - raise "Internal error: making illegal join step" if role_ref.role.object_type != role_node.object_type - join_jr = @constellation.JoinRole(join_node, join_role) + + raise "Internal error in #{constraint_type} #{name}: making illegal reference to join node, end role mismatch" if end_role.object_type != end_node.object_type + rr = @constellation.RoleRef(replacement_rs, replacement_rs.all_role_ref.size, :role => end_role) + projecting_jr ||= (constrained_join_role = @constellation.JoinRole(end_node, end_role)) + projecting_jr.role_ref = rr # Project this RoleRef + + if join_over + if !join_node # Create the JoinNode when processing the first role + debug :join, "Join Node #{join.all_join_node.size} is over #{join_over.name}" + join_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => join_over) + end + debug :join, "Making join step from #{end_point.name} to #{join_over.name}" do + rs = @constellation.RoleSequence(:new) + # Detect the fact type over which we're joining (may involve objectification) + raise "Internal error in #{constraint_type} #{name}: making illegal reference to join node, object type mismatch" if role_ref.role.object_type != role_node.object_type + @constellation.RoleRef(rs, 0, :role => role_ref.role) role_jr = @constellation.JoinRole(role_node, role_ref.role) - js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type) - roles -= [join_role, role_ref.role] - roles.each do |incidental_role| - jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => incidental_role.object_type) - jr = @constellation.JoinRole(jn, incidental_role, :join_step => js) - end - else - if role_sequence.all_role_ref.size > 1 - join_node = role_node - join_role = role_ref.role - else - # There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why? - # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why. - # I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example) + raise "Internal error in #{constraint_type} #{name}: making illegal reference to join node, joined_role mismatch" if joined_role.object_type != join_node.object_type + @constellation.RoleRef(rs, 1, :role => joined_role) + join_jr = @constellation.JoinRole(join_node, joined_role) + + js = @constellation.JoinStep(role_jr, join_jr, :fact_type => joined_role.fact_type) + debug :join, "New join step #{js.describe}" + end + else + debug :join, "Need join step for non-join_over role #{end_point.name} #{role_ref.describe} in #{role_ref.role.fact_type.default_reading}" + if (roles = role_ref.role.fact_type.all_role.to_a).size > 1 + # Here we have an end join (step already created) but no sequence join + if join_node + raise "Internal error in #{constraint_type} #{name}: making illegal join step" if role_ref.role.object_type != role_node.object_type + join_jr = @constellation.JoinRole(join_node, join_role) role_jr = @constellation.JoinRole(role_node, role_ref.role) - js = nil - role_ref.role.fact_type.all_role.each do |role| - next if role == role_jr.role - next if role_sequence.all_role_ref.detect{|rr| rr.role == role} - jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => role.object_type) - jr = @constellation.JoinRole(jn, role) - if js - jr.join_step = js # Incidental role - else - js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type) + js = @constellation.JoinStep(join_jr, role_jr, :fact_type => role_ref.role.fact_type) + roles -= [join_role, role_ref.role] + roles.each do |incidental_role| + jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => incidental_role.object_type) + jr = @constellation.JoinRole(jn, incidental_role, :join_step => js) + end + else + if role_sequence.all_role_ref.size > 1 + join_node = role_node + join_role = role_ref.role + else + # There's no join in this role sequence, so we'd drop of fthe bottom without doing the right things. Why? + # Without this case, Supervision.orm omits "that runs Company" from the exclusion constraint, and I'm not sure why. + # I think the "then" code causes it to drop out the bottom without making the step (which is otherwise made in every case, see CompanyDirectorEmployee for example) + role_jr = @constellation.JoinRole(role_node, role_ref.role) + js = nil + role_ref.role.fact_type.all_role.each do |role| + next if role == role_jr.role + next if role_sequence.all_role_ref.detect{|rr| rr.role == role} + jn = @constellation.JoinNode(join, join.all_join_node.size, :object_type => role.object_type) + jr = @constellation.JoinRole(jn, role) + if js + jr.join_step = js # Incidental role + else + js = @constellation.JoinStep(role_jr, jr, :fact_type => role_ref.role.fact_type) + end end end end + else + # Unary fact type, make a Join Step from and to the constrained_join_role + jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role) + js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type) end - else - # Unary fact type, make a Join Step from and to the constrained_join_role - jr = @constellation.JoinRole(constrained_join_role.join_node, role_ref.role) - js = @constellation.JoinStep(jr, jr, :fact_type => role_ref.role.fact_type) end end - end - # Thoroughly check that this is a valid join - join.validate - debug :join, "Join has projected nodes #{replacement_rs.describe}" + # Thoroughly check that this is a valid join + join.validate + debug :join, "Join has projected nodes #{replacement_rs.describe}" - # Constrain the replacement role sequence, which has the attached join: - role_sequences[role_sequences.index(role_sequence)] = replacement_rs + # Constrain the replacement role sequence, which has the attached join: + role_sequences[role_sequences.index(role_sequence)] = replacement_rs + end + return true end + rescue => e + $stderr.puts '// '+e.to_s + return false end end def read_exclusion_constraints x_exclusion_constraints = @x_model.xpath("orm:Constraints/orm:ExclusionConstraint") - debug :orm, "Reading exclusion constraints" do + debug :orm, "Reading #{x_exclusion_constraints.size} exclusion constraints" do x_exclusion_constraints.each{|x| id = x['id'] name = x["Name"] || '' name = nil if name.size == 0 x_mandatory = (m = x.xpath("orm:ExclusiveOrMandatoryConstraint")[0]) && @@ -1006,11 +1048,11 @@ @mandatory_constraints_by_rs.delete(mc_rs) end next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role - make_joins('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences) + next unless make_joins('exclusion', name+(x_mandatory ? '/'+x_mandatory['Name'] : ''), role_sequences) ec = @constellation.SetExclusionConstraint(:new) ec.vocabulary = @vocabulary ec.name = name # ec.enforcement = @@ -1038,13 +1080,15 @@ x_role_refs , # .map{|xr| @x_by_id[xr['ref']] }, "equality constraint #{name}" ) } - next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role - make_joins('equality', name, role_sequences) + # Role sequence missing; includes a derived fact type role + next if role_sequences.compact.size != role_sequences.size + next unless make_joins('equality', name, role_sequences) + ec = @constellation.SetEqualityConstraint(:new) ec.vocabulary = @vocabulary ec.name = name # ec.enforcement = role_sequences.each_with_index do |rs, i| @@ -1069,11 +1113,11 @@ x_role_refs , # .map{|xr| @x_by_id[xr['ref']] }, "equality constraint #{name}" ) } next if role_sequences.compact.size != role_sequences.size # Role sequence missing; includes a derived fact type role - make_joins('subset', name, role_sequences) + next unless make_joins('subset', name, role_sequences) ec = @constellation.SubsetConstraint(:new) ec.vocabulary = @vocabulary ec.name = name # ec.enforcement = @@ -1294,33 +1338,38 @@ subject = @by_id[x_subject["ref"]] is_expanded = v = x_shape['IsExpanded'] and v == 'true' bounds = x_shape['AbsoluteBounds'] case shape_type = x_shape.name when 'FactTypeShape' - read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject + if subject + read_fact_type_shape diagram, x_shape, is_expanded, bounds, subject + # else REVISIT: probably a derived fact type + end when 'ExternalConstraintShape', 'FrequencyConstraintShape' # REVISIT: The offset might depend on the constraint type. This is right for subset and other round ones. - position = convert_position(bounds, Gravity::NW, 31, 31) + position = convert_position(bounds, Gravity::C) shape = @constellation.ConstraintShape( :new, :diagram => diagram, :position => position, :is_expanded => is_expanded, :constraint => subject ) when 'RingConstraintShape' # REVISIT: The offset might depend on the ring constraint type. This is right for basic round ones. - position = convert_position(bounds, Gravity::NW, 31, 31) + position = convert_position(bounds, Gravity::C) shape = @constellation.RingConstraintShape( :new, :diagram => diagram, :position => position, :is_expanded => is_expanded, :constraint => subject ) shape.fact_type = subject.role.fact_type when 'ModelNoteShape' # REVISIT: Add model notes when 'ObjectTypeShape' + position = convert_position(bounds, Gravity::C) + # $stderr.puts "#{subject.name}: bounds=#{bounds} -> position = (#{position.x}, #{position.y})" shape = @constellation.ObjectTypeShape( :new, :diagram => diagram, :position => position, :is_expanded => is_expanded, :object_type => subject, - :has_expanded_reference_mode => false # REVISIT + :position => position ) else raise "Unknown shape #{x_shape.name}" end end @@ -1340,25 +1389,26 @@ case v when 'VerticalRotatedLeft'; 'left' when 'VerticalRotatedRight'; 'right' else nil end - # Position of a fact type is the top-left of the first role box - offs_x = 0 - offs_y = 0 - if fact_type.entity_type # If objectified, move right 12, down 24 - offs_x += 12 - offs_y += 24 + + # Position of a fact type is the centre of the row of role boxes + offs_x = 11 + offs_y = -12 + if fact_type.entity_type + offs_x -= 12 + offs_y -= 9 if !fact_type.entity_type.is_implied_by_objectification end - # count internal UC's, add 27 Y units for each: - iucs = fact_type.internal_presence_constraints.select{|uc| uc.max_frequency == 1 } - offs_y += iucs.size*27 - position = convert_position(bounds, Gravity::NW, offs_x, offs_y) + position = convert_position(bounds, Gravity::S, offs_x, offs_y) + + # $stderr.puts "#{fact_type.describe}: bounds=#{bounds} -> position = (#{position.x}, #{position.y})" + debug :orm, "REVISIT: Can't place rotated fact type correctly on diagram yet" if rotation_setting - debug :orm, "fact type at #{position.x},#{position.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}, #{iucs.size} IUC's" + debug :orm, "fact type at #{position.x},#{position.y} has display_role_names_setting=#{display_role_names_setting.inspect}, rotation_setting=#{rotation_setting.inspect}" shape = @constellation.FactTypeShape( :new, :diagram => diagram, :position => position, :is_expanded => is_expanded, @@ -1384,13 +1434,13 @@ relative_shapes = x_shape.xpath('ormDiagram:RelativeShapes/*') relative_shapes.each do |xr_shape| position = convert_position(xr_shape['AbsoluteBounds']) case xr_shape.name when 'ObjectifiedFactTypeNameShape' - @constellation.ObjectifiedFactTypeNameShape(shape, :diagram => diagram, :position => position, :is_expanded => false) + @constellation.ObjectifiedFactTypeNameShape(shape, :shape_id => :new, :diagram => diagram, :position => position, :is_expanded => false) when 'ReadingShape' - @constellation.ReadingShape(:new, :diagram => diagram, :position => position, :is_expanded => false, :reading => fact_type.preferred_reading) + @constellation.ReadingShape(shape, :shape_id => :new, :fact_type_shape=>shape, :diagram => diagram, :position => position, :is_expanded => false, :reading => fact_type.preferred_reading) when 'RoleNameShape' role = @by_id[xr_shape.xpath("ormDiagram:Subject")[0]['ref']] role_display = role_display_for_role(shape, x_role_display, role) debug :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name}" @constellation.RoleNameShape( @@ -1401,11 +1451,11 @@ vc_subject_id = xr_shape.xpath("ormDiagram:Subject")[0]['ref'] constraint = @by_id[vc_subject_id] debug :orm, "Fact type '#{fact_type.preferred_reading.expand}' has #{xr_shape.name} for #{constraint.inspect}" role_display = role_display_for_role(shape, x_role_display, constraint.role) - debug :orm, "ValueConstraintShape is on #{role_ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})" + debug :orm, "ValueConstraintShape is on #{role_display.ordinal}'th role (by #{x_role_display.size > 0 ? 'role_display' : 'fact roles'})" @constellation.ValueConstraintShape( :new, :diagram => diagram, :position => position, :is_expanded => false, :constraint => constraint, :object_type_shape => nil, # This constraint is relative to a Fact Type, so must be on a role :role_display => role_display @@ -1416,28 +1466,33 @@ end # Find or create the RoleDisplay for this role in this fact_type_shape, given (possibly empty) x_role_display nodes: def role_display_for_role(fact_type_shape, x_role_display, role) if x_role_display.size == 0 - role_ordinal = fact_type_shape.fact_type.all_role.to_a.index(role) + # There's no x_role_display, which means the roles are in displayed + # the same order as in the fact type. However, we need a RoleDisplay + # to attach a ReadingShape or ValueConstraintShape, so make them all. + fact_type_shape.fact_type.all_role.each{|r| @constellation.RoleDisplay(fact_type_shape, r.ordinal, :role => r) } + role_ordinal = fact_type_shape.fact_type.all_role_in_order.index(role) else role_ordinal = x_role_display.map{|rd| @by_id[rd['ref']]}.index(role) end role_display = @constellation.RoleDisplay(fact_type_shape, role_ordinal, :role => role) end - DIAGRAM_SCALE = 384 - def convert_position(bounds, gravity = Gravity::NW, xoffs = 0, yoffs = 0) + DIAGRAM_SCALE = 96*1.5 + def convert_position(bounds, gravity = Gravity::C, xoffs = 0, yoffs = 0) return nil unless bounds + # Bounds is top, left, width, height in inches bf = bounds.split(/, /).map{|b|b.to_f} sizefrax = [ [0, 0], [1, 0], [2, 0], [0, 1], [1, 1], [2, 2], [0, 2], [1, 2], [2, 2], ] - x = (DIAGRAM_SCALE * bf[0]+bf[2]*sizefrax[gravity][0]/2).round + xoffs - y = (DIAGRAM_SCALE * bf[1]+bf[3]*sizefrax[gravity][1]/2).round + yoffs + x = (DIAGRAM_SCALE * (bf[0]+bf[2]*sizefrax[gravity][0]/2)).round + xoffs + y = (DIAGRAM_SCALE * (bf[1]+bf[3]*sizefrax[gravity][1]/2)).round + yoffs @constellation.Position(x, y) end # Detect numeric data and denote it as a string: def is_a_string(value)