<commit_before>#
# ActiveFacts Compositions, Fundamental Compositor
#
#       All Compositors derive from this one, which can calculate the basic binary bi-directional mapping
#
#       The term "reference" used here means either an Absorption
#       (one direction of a binary fact type relating two object types),
#       or an Indicator (for a unary fact type).
#
#       n-ary fact types and other objectified fact types are factored out by using the associated LinkFactTypes.
#
# Copyright (c) 2015 <NAME>. Read the LICENSE file.
#
require "activefacts/metamodel"

require 'byebug'

module ActiveFacts
  module Compositions
    class Compositor
      attr_reader :options, :name, :composition

      def self.options
        {
          source: ['Boolean', "Generate composition for source model"],
          target: ['Boolean', "Generate composition for target model"]
        }
      end

      def initialize constellation, name, options = {}
        @constellation = constellation
        @name = name
        @options = options
        @option_source = options.delete('source')
        @option_target = options.delete('target')
        @option_transform = options.delete('transform')
      end

      # Generate all Mappings into @binary_mappings for a binary composition of all ObjectTypes in this constellation
      def generate
        # Retract an existing composition by this name
        if existing = @constellation.Name[[@name]] and
            @composition = existing.composition
          @composition.all_composite.to_a.each{|composite| composite.retract}
          @composition.retract
        end

        @composition = @constellation.Composition(:new, :name => @name)
        preload_preferred_identifiers
        populate_references
      end

    private
      # Preferred identifiers are cached, but the process produces trace output
      # that appears in the "tutti" mode used in testing. This precludes that.
      def preload_preferred_identifiers
        @constellation.EntityType.map{|k, et| et.preferred_identifier }
      end

      def populate_reference object_type, role
        parent = @binary_mappings[role.object_type]

        return if role.fact_type.all_role.size > 2
        if role.fact_type.all_role.size != 1
          counterpart = role.counterpart
          rt = role_type(counterpart)
          if rt == :many_many
            raise "Fact type must be objectified: #{role.fact_type.default_reading}"
          end

          a = @constellation.Absorption(
              :new,
              name: role_name(counterpart),
              parent: parent,
              object_type: counterpart.object_type,
              parent_role: role,
              child_role: counterpart
            )
          # Populate the absorption/reverse_absorption (putting the "many" or optional side as reverse)
          if r = @component_by_fact[role.fact_type]
            # Second occurrence of this fact type, set the direction:
            if a.is_preferred_direction
              a.reverse_absorption = r
            else  # Set this as the reverse absorption
              a.forward_absorption = r
            end
          else
            # First occurrence of this fact type
            @component_by_fact[role.fact_type] = a
          end
        else    # It's an indicator
          a = @constellation.Indicator(
              :new,
              name: role.name,
              parent: parent,
              role: role
            )
          @component_by_fact[role.fact_type] = a  # For completeness, in case a subclass uses it
        end
        trace :binarize, "Populating #{a.inspect}"
      end

      def find_topic import_role
        if import = @constellation.Import.values.select{ |import| import.import_role == import_role }.first
          import.precursor_topic
        else
          nil
        end
      end

      def build_binary_mappings model_topic
        trace :binarize, "Build_binary_mappings for #{model_topic.topic_name}"
        if @model_topics.key?(model_topic)
          trace :binarize, "already built, skip"
          return
        end
        @model_topics[model_topic] = true

        # add binary mapping for all object types in this model
        model_topic.all_concept.each do |concept|
          if concept.object_type
            @binary_mappings[concept.object_type]
          end
        end

        # recurse through precursor models
        model_topic.all_import.each do |import|
          build_binary_mappings(import.precursor_topic)
        end
      end

      def populate_references
        # A table of Mappings by object type, with a default Mapping for each:
        @binary_mappings = Hash.new do |h, object_type|
          h[object_type] = @constellation.Mapping(
              :new,
              name: object_type.name,
              object_type: object_type
            )
        end
        @component_by_fact = {}

        @model_topics = {}
        if @option_source || @option_target
          import_role = @option_source ? 'source' : 'target'
          trace :binarize, "Build binary bindings for #{import_role} model" do
            if model_topic = find_topic(import_role)
              build_binary_mappings(model_topic)
            else
              raise "Could not find #{import_role} model"
            end
          end
        elsif @option_transform
          trace :binarize, "Build binary bindings for transform model" do
            transform_topic = @constellation.Topic.values.select do |topic|
              @constellation.Import.values.select{|i| i.precursor_topic == topic}.size == 0
            end.first

            # add binary mapping for all object types in the transform model
            transform_topic.all_concept.each do |concept|
              if concept.object_type
                @binary_mappings[concept.object_type]
              end
            end
          end
        else
          trace :binarize, "Build binary bindings for full model" do
            @constellation.ObjectType.each do |key, object_type|
              @binary_mappings[object_type]  # Ensure we create the top Mapping even if it has no references
            end
          end
        end

        @binary_mappings.each do |object_type, mapping|
          trace :binarize, "Populating possible absorptions for #{object_type.name}" do

            object_type.all_role.each do |role|

              # byebug if role.counterpart.name == "IJC ID"

              # Exclude fact types not in @model_topics
              next if @model_topics.size > 0 && !@model_topics.key?(role.fact_type.concept.topic)
              # Exclude base roles in objectified fact types (unless unary); just use link fact types
              next if role.fact_type.entity_type && role.fact_type.all_role.size != 1
              next if role.variable   # REVISIT: Continue to ignore roles in derived fact types?
              populate_reference object_type, role
            end
            if object_type.is_a?(ActiveFacts::Metamodel::ValueType)
              # This requires a change in the metamodel to use TypeInheritance for ValueTypes
              if object_type.supertype
                trace :binarize, "REVISIT: Eliding supertype #{object_type.supertype.name} for #{object_type.name}"
              end
              object_type.all_value_type_as_supertype.each do |subtype|
                trace :binarize, "REVISIT: Eliding subtype #{subtype.name} for #{object_type.name}"
              end<commit_msg>Eliminated byebug<commit_after>#
# ActiveFacts Compositions, Fundamental Compositor
#
#       All Compositors derive from this one, which can calculate the basic binary bi-directional mapping
#
#       The term "reference" used here means either an Absorption
#       (one direction of a binary fact type relating two object types),
#       or an Indicator (for a unary fact type).
#
#       n-ary fact types and other objectified fact types are factored out by using the associated LinkFactTypes.
#
# Copyright (c) 2015 <NAME>. Read the LICENSE file.
#
require "activefacts/metamodel"

module ActiveFacts
  module Compositions
    class Compositor
      attr_reader :options, :name, :composition

      def self.options
        {
          source: ['Boolean', "Generate composition for source model"],
          target: ['Boolean', "Generate composition for target model"]
        }
      end

      def initialize constellation, name, options = {}
        @constellation = constellation
        @name = name
        @options = options
        @option_source = options.delete('source')
        @option_target = options.delete('target')
        @option_transform = options.delete('transform')
      end

      # Generate all Mappings into @binary_mappings for a binary composition of all ObjectTypes in this constellation
      def generate
        # Retract an existing composition by this name
        if existing = @constellation.Name[[@name]] and
            @composition = existing.composition
          @composition.all_composite.to_a.each{|composite| composite.retract}
          @composition.retract
        end

        @composition = @constellation.Composition(:new, :name => @name)
        preload_preferred_identifiers
        populate_references
      end

    private
      # Preferred identifiers are cached, but the process produces trace output
      # that appears in the "tutti" mode used in testing. This precludes that.
      def preload_preferred_identifiers
        @constellation.EntityType.map{|k, et| et.preferred_identifier }
      end

      def populate_reference object_type, role
        parent = @binary_mappings[role.object_type]

        return if role.fact_type.all_role.size > 2
        if role.fact_type.all_role.size != 1
          counterpart = role.counterpart
          rt = role_type(counterpart)
          if rt == :many_many
            raise "Fact type must be objectified: #{role.fact_type.default_reading}"
          end

          a = @constellation.Absorption(
              :new,
              name: role_name(counterpart),
              parent: parent,
              object_type: counterpart.object_type,
              parent_role: role,
              child_role: counterpart
            )
          # Populate the absorption/reverse_absorption (putting the "many" or optional side as reverse)
          if r = @component_by_fact[role.fact_type]
            # Second occurrence of this fact type, set the direction:
            if a.is_preferred_direction
              a.reverse_absorption = r
            else  # Set this as the reverse absorption
              a.forward_absorption = r
            end
          else
            # First occurrence of this fact type
            @component_by_fact[role.fact_type] = a
          end
        else    # It's an indicator
          a = @constellation.Indicator(
              :new,
              name: role.name,
              parent: parent,
              role: role
            )
          @component_by_fact[role.fact_type] = a  # For completeness, in case a subclass uses it
        end
        trace :binarize, "Populating #{a.inspect}"
      end

      def find_topic import_role
        if import = @constellation.Import.values.select{ |import| import.import_role == import_role }.first
          import.precursor_topic
        else
          nil
        end
      end

      def build_binary_mappings model_topic
        trace :binarize, "Build_binary_mappings for #{model_topic.topic_name}"
        if @model_topics.key?(model_topic)
          trace :binarize, "already built, skip"
          return
        end
        @model_topics[model_topic] = true

        # add binary mapping for all object types in this model
        model_topic.all_concept.each do |concept|
          if concept.object_type
            @binary_mappings[concept.object_type]
          end
        end

        # recurse through precursor models
        model_topic.all_import.each do |import|
          build_binary_mappings(import.precursor_topic)
        end
      end

      def populate_references
        # A table of Mappings by object type, with a default Mapping for each:
        @binary_mappings = Hash.new do |h, object_type|
          h[object_type] = @constellation.Mapping(
              :new,
              name: object_type.name,
              object_type: object_type
            )
        end
        @component_by_fact = {}

        @model_topics = {}
        if @option_source || @option_target
          import_role = @option_source ? 'source' : 'target'
          trace :binarize, "Build binary bindings for #{import_role} model" do
            if model_topic = find_topic(import_role)
              build_binary_mappings(model_topic)
            else
              raise "Could not find #{import_role} model"
            end
          end
        elsif @option_transform
          trace :binarize, "Build binary bindings for transform model" do
            transform_topic = @constellation.Topic.values.select do |topic|
              @constellation.Import.values.select{|i| i.precursor_topic == topic}.size == 0
            end.first

            # add binary mapping for all object types in the transform model
            transform_topic.all_concept.each do |concept|
              if concept.object_type
                @binary_mappings[concept.object_type]
              end
            end
          end
        else
          trace :binarize, "Build binary bindings for full model" do
            @constellation.ObjectType.each do |key, object_type|
              @binary_mappings[object_type]  # Ensure we create the top Mapping even if it has no references
            end
          end
        end

        @binary_mappings.each do |object_type, mapping|
          trace :binarize, "Populating possible absorptions for #{object_type.name}" do

            object_type.all_role.each do |role|
              # Exclude fact types not in @model_topics
              next if @model_topics.size > 0 && !@model_topics.key?(role.fact_type.concept.topic)
              # Exclude base roles in objectified fact types (unless unary); just use link fact types
              next if role.fact_type.entity_type && role.fact_type.all_role.size != 1
              next if role.variable   # REVISIT: Continue to ignore roles in derived fact types?
              populate_reference object_type, role
            end
            if object_type.is_a?(ActiveFacts::Metamodel::ValueType)
              # This requires a change in the metamodel to use TypeInheritance for ValueTypes
              if object_type.supertype
                trace :binarize, "REVISIT: Eliding supertype #{object_type.supertype.name} for #{object_type.name}"
              end
              object_type.all_value_type_as_supertype.each do |subtype|
                trace :binarize, "REVISIT: Eliding subtype #{subtype.name} for #{object_type.name}"
              end
{'fraction_non_alphanumeric': 0.06880181866809307, 'fraction_numerical': 0.0012035303557100829, 'mean_word_length': 2.0750411184210527, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '357963', 'n_tokens_mistral': 4057, 'n_tokens_neox': 3872, 'n_words': 1195}