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)