<commit_before>require 'caruby/util/collection'
require 'caruby/import/java'
require 'caruby/domain/java_attribute_metadata'
require 'caruby/domain/resource_introspection'
require 'caruby/domain/resource_inverse'
require 'caruby/domain/resource_dependency'
require 'caruby/domain/resource_attributes'

module CaRuby
  # Exception raised if a meta-data setting is missing or invalid.
  class MetadataError < RuntimeError; end
  
  # Adds introspected metadata to a Class.
  module ResourceMetadata
    include ResourceIntrospection, ResourceInverse, ResourceDependency, ResourceAttributes

    attr_reader :domain_module

    # Associates meta-data with a Resource class. When a Resource class is added to a ResourceModule,
    # the ResourceModule extends the Resource class with ResourceMetadata and calls this
    # {#add_metadata} method with itself as the mod argument. The ResourceModule is accessed
    # by the {#domain_module} method.
    def add_metadata(mod)
      @domain_module = mod
      introspect # in ResourceIntrospection
    end

    # @return the domain type for attribute, or nil if attribute is not a domain attribute
    def domain_type(attribute)
      attr_md = attribute_metadata(attribute)
      attr_md.type if attr_md.domain?
    end

    # Returns an empty value for the given attribute.
    # * If this class is not abstract, then the empty value is the initialized value.
    # * Otherwise, if the attribute is a Java primitive number then zero.
    # * Otherwise, if the attribute is a Java primitive boolean then +false+.
    # * Otherwise, the empty value is nil.
    #
    # @param [Symbol] attribute the target attribute
    # @return [Numeric, Boolean, Enumerable, nil] the empty attribute value
    def empty_value(attribute)
      if abstract? then
        attr_md = attribute_metadata(attribute)
        # the Java property type
        jtype = attr_md.property_descriptor.property_type if JavaAttributeMetadata === attr_md
        # A primitive is either a boolean or a number (String is not primitive).
        if jtype and jtype.primitive? then
          type.name == 'boolean' ? false : 0
        end
      else
        # Since this class is not abstract, create a prototype instance on demand and make
        # a copy of the initialized collection value from that instance.
        @prototype ||= new
        value = @prototype.send(attribute) || return
        value.class.new
      end
    end
    
   # Prints this classifier's content to the log.
    def pretty_print(q)
      
      
      # KLUDGE - if not inited then bail
      if @attr_md_hash.nil? then
        q.text(qp)
        return
      end
      
      
      # the Java property descriptors
      property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
      # build a map of relevant display label => attributes
      prop_printer = property_descriptors.wrap { |pd| PROP_DESC_PRINTER.wrap(pd) }
      prop_syms = property_descriptors.map { |pd| pd.name.to_sym }.to_set
      aliases = @alias_std_attr_map.keys - attributes.to_a - prop_syms
      alias_attr_hash = aliases.to_compact_hash { |aliaz| @alias_std_attr_map[aliaz] }
      dependents_printer = dependent_attributes.wrap { |attr| DEPENDENT_ATTR_PRINTER.wrap(attribute_metadata(attr)) }
      owner_printer = owners.wrap { |type| TYPE_PRINTER.wrap(type) }
      inverses = @attributes.to_compact_hash do |attr|
         attr_md = attribute_metadata(attr)
         "#{attr_md.type.qp}.#{attr_md.inverse}" if attr_md.inverse
      end
      domain_attr_printer = domain_attributes.to_compact_hash { |attr| domain_type(attr).qp }
      map = {
        "Java properties" => prop_printer,
        "standard attributes" => attributes,
        "aliases to standard attributes" => alias_attr_hash,
        "secondary key" => secondary_key_attributes,
        "mandatory attributes" => mandatory_attributes,
        "domain attributes" => domain_attr_printer,
        "creatable domain attributes" => creatable_domain_attributes,
        "updatable domain attributes" => updatable_domain_attributes,
        "fetched domain attributes" => fetched_domain_attributes,
        "cascaded domain attributes" => cascaded_attributes,
        "owners" => owner_printer,
        "owner attributes" => owner_attributes,
        "inverse attributes" => inverses,
        "dependent attributes" => dependents_printer,
        "default values" => defaults
      }.delete_if { |key, value| value.nil_or_empty? }
      # one indented line per entry, all but the last line ending in a comma
      content = map.map { |label, value| "  #{label}=>#{format_print_value(value)}" }.join(",\n")
      # print the content to the log
      q.text("#{qp} structure:\n#{content}")
    end

    protected

    def self.extend_class(klass, mod)
      klass.extend(self)
      klass.add_metadata(mod)
    end

    private

    # A proc to print the unqualified class name.
    TYPE_PRINTER = PrintWrapper.new { |type| type.qp }

    DEPENDENT_ATTR_PRINTER = PrintWrapper.new do |attr_md|
      flags = []
      flags << :logical if attr_md.logical?
      flags << :autogenerated if attr_md.autogenerated?
      flags << :disjoint if attr_md.disjoint?
      flags.empty? ? "#{attr_md}" : "#{attr_md}(#{flags.join(',')})"
    end

    # A proc to print the property descriptor name.
    PROP_DESC_PRINTER = PrintWrapper.new { |pd| pd.name }

    def format_print_value(value)
      case value
        when String then value
        when Class then value.qp
        else value.pp_s(:single_line)
      end
    end
  end
end<commit_msg>Add opts to new method in the extended call-back.<commit_after>require 'caruby/util/collection'
require 'caruby/import/java'
require 'caruby/domain/java_attribute_metadata'
require 'caruby/domain/resource_introspection'
require 'caruby/domain/resource_inverse'
require 'caruby/domain/resource_dependency'
require 'caruby/domain/resource_attributes'

module CaRuby
  # Exception raised if a meta-data setting is missing or invalid.
  class MetadataError < RuntimeError; end
  
  # Adds introspected metadata to a Class.
  module ResourceMetadata
    include ResourceIntrospection, ResourceInverse, ResourceDependency, ResourceAttributes

    attr_reader :domain_module
      
    # JRuby 1.6 alert - if a concrete class implements a constructor, then calling super in an
    # included module initialize method results in a recursive call back into that initialize. 
    # The work-around is to redefine each domain class new method to call the initalize_content
    # method instead. All Resource domain classes or included modules must not override
    # initialize. They can implement initialize_content instead.
    def self.extended(klass)
      klass.class_eval do
        class << self
          def new(opts=nil)
            obj = super()
            obj.merge_attributes(opts) if opts
            obj
          end
        end
      end
    end

    # Associates meta-data with a Resource class. When a Resource class is added to a ResourceModule,
    # the ResourceModule extends the Resource class with ResourceMetadata and calls this
    # {#add_metadata} method with itself as the mod argument. The ResourceModule is accessed
    # by the {#domain_module} method.
    def add_metadata(mod)
      @domain_module = mod
      introspect # in ResourceIntrospection
    end

    # @return the domain type for attribute, or nil if attribute is not a domain attribute
    def domain_type(attribute)
      attr_md = attribute_metadata(attribute)
      attr_md.type if attr_md.domain?
    end

    # Returns an empty value for the given attribute.
    # * If this class is not abstract, then the empty value is the initialized value.
    # * Otherwise, if the attribute is a Java primitive number then zero.
    # * Otherwise, if the attribute is a Java primitive boolean then +false+.
    # * Otherwise, the empty value is nil.
    #
    # @param [Symbol] attribute the target attribute
    # @return [Numeric, Boolean, Enumerable, nil] the empty attribute value
    def empty_value(attribute)
      if abstract? then
        attr_md = attribute_metadata(attribute)
        # the Java property type
        jtype = attr_md.property_descriptor.property_type if JavaAttributeMetadata === attr_md
        # A primitive is either a boolean or a number (String is not primitive).
        if jtype and jtype.primitive? then
          type.name == 'boolean' ? false : 0
        end
      else
        # Since this class is not abstract, create a prototype instance on demand and make
        # a copy of the initialized collection value from that instance.
        @prototype ||= new
        value = @prototype.send(attribute) || return
        value.class.new
      end
    end
    
   # Prints this classifier's content to the log.
    def pretty_print(q)
      # KLUDGE - if not inited then bail.
      # TODO - isolate when this occurs.
      if @attr_md_hash.nil? then
        q.text(qp)
        return
      end
      
      # the Java property descriptors
      property_descriptors = java_attributes.wrap { |attr| attribute_metadata(attr).property_descriptor }
      # build a map of relevant display label => attributes
      prop_printer = property_descriptors.wrap { |pd| PROP_DESC_PRINTER.wrap(pd) }
      prop_syms = property_descriptors.map { |pd| pd.name.to_sym }.to_set
      aliases = @alias_std_attr_map.keys - attributes.to_a - prop_syms
      alias_attr_hash = aliases.to_compact_hash { |aliaz| @alias_std_attr_map[aliaz] }
      dependents_printer = dependent_attributes.wrap { |attr| DEPENDENT_ATTR_PRINTER.wrap(attribute_metadata(attr)) }
      owner_printer = owners.wrap { |type| TYPE_PRINTER.wrap(type) }
      inverses = @attributes.to_compact_hash do |attr|
         attr_md = attribute_metadata(attr)
         "#{attr_md.type.qp}.#{attr_md.inverse}" if attr_md.inverse
      end
      domain_attr_printer = domain_attributes.to_compact_hash { |attr| domain_type(attr).qp }
      map = {
        "Java properties" => prop_printer,
        "standard attributes" => attributes,
        "aliases to standard attributes" => alias_attr_hash,
        "secondary key" => secondary_key_attributes,
        "mandatory attributes" => mandatory_attributes,
        "domain attributes" => domain_attr_printer,
        "creatable domain attributes" => creatable_domain_attributes,
        "updatable domain attributes" => updatable_domain_attributes,
        "fetched domain attributes" => fetched_domain_attributes,
        "cascaded domain attributes" => cascaded_attributes,
        "owners" => owner_printer,
        "owner attributes" => owner_attributes,
        "inverse attributes" => inverses,
        "dependent attributes" => dependents_printer,
        "default values" => defaults
      }.delete_if { |key, value| value.nil_or_empty? }
      
      # one indented line per entry, all but the last line ending in a comma
      content = map.map { |label, value| "  #{label}=>#{format_print_value(value)}" }.join(",\n")
      # print the content to the log
      q.text("#{qp} structure:\n#{content}")
    end

    protected

    def self.extend_class(klass, mod)
      klass.extend(self)
      klass.add_metadata(mod)
    end

    private

    # A proc to print the unqualified class name.
    TYPE_PRINTER = PrintWrapper.new { |type| type.qp }

    DEPENDENT_ATTR_PRINTER = PrintWrapper.new do |attr_md|
      flags = []
      flags << :logical if attr_md.logical?
      flags << :autogenerated if attr_md.autogenerated?
      flags << :disjoint if attr_md.disjoint?
      flags.empty? ? "#{attr_md}" : "#{attr_md}(#{flags.join(',')})"
    end

    # A proc to print the property descriptor name.
    PROP_DESC_PRINTER = PrintWrapper.new { |pd| pd.name }

    def format_print_value(value)
      case value
        when String then value
        when Class then value.qp
        else value.pp_s(:single_line)
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.08068153345026309, 'fraction_numerical': 0.00033408502463877054, 'mean_word_length': 3.0006682258603408, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 37, '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': '4996676', 'n_tokens_mistral': 3408, 'n_tokens_neox': 3350, 'n_words': 1062}