<commit_before>module Xcodeproj
  class Project
    class UUIDGenerator
      def initialize(project)
        @project = project
        @new_objects_by_uuid = {}
        @paths_by_object = {}
      end

      def generate!
        all_objects = @project.objects
        generate_paths(@project.root_object)
        switch_uuids(all_objects)
        verify_no_duplicates!(all_objects)
        fixup_uuid_references
        @project.instance_variable_set(:@generated_uuids, @project.instance_variable_get(:@available_uuids))
        @project.instance_variable_set(:@objects_by_uuid, @new_objects_by_uuid)
      end

      private

      def verify_no_duplicates!(all_objects)
        duplicates = all_objects - @new_objects_by_uuid.values
        UserInterface.warn "[Xcodeproj] Generated duplicate UUIDs:\n\n" <<
          duplicates.map { |d| "#{d.isa} -- #{@paths_by_object[d]}" }.join("\n") unless duplicates.empty?
      end

      def fixup_uuid_references
        fixup = ->(object, attr) do
          if object.respond_to?(attr) && link = @project.objects_by_uuid[object.send(attr)]
            object.send(:"#{attr}=", link.uuid)
          end
        end
        @project.objects.each do |object|
          [:remote_global_id_string, :container_portal, :target_proxy].each do |attr|
            fixup[object, attr]
          end
        end
      end

      def generate_paths(object, path = '')
        @paths_by_object[object] = path

        object.to_one_attributes.each do |attrb|
          obj = attrb.get_value(object)
          generate_paths(obj, path + '/' << attrb.plist_name) if obj
        end

        object.to_many_attributes.each do |attrb|
          attrb.get_value(object).each do |o|
            generate_paths(o, path + '/' << attrb.plist_name << "/#{path_component_for_object(o)}")
          end
        end

        object.references_by_keys_attributes.each do |attrb|
          attrb.get_value(object).each do |dictionary|
            dictionary.each do |key, value|
              generate_paths(value, path + '/' << attrb.plist_name << "/k:#{key}/#{path_component_for_object(value)}")
            end
          end
        end
      end

      def switch_uuids(objects)
        objects.each do |object|
          next unless path = @paths_by_object[object]
          uuid = uuid_for_path(path)
          object.instance_variable_set(:@uuid, uuid)
          @new_objects_by_uuid[uuid] = object
        end
      end

      def uuid_for_path(path)
        Digest::MD5.hexdigest(path).upcase
      end

      def path_component_for_object(object)
        hash = object.to_tree_hash
        component = tree_hash_to_path(hash)
        component << object.hierarchy_path.to_s if object.respond_to?(:hierarchy_path)
        component
      end

      def tree_hash_to_path(object, depth = 4)
        return '|' if depth.zero?
        case object
        when Hash<commit_msg>[UUIDGenerator] Cache some path calculations<commit_after>module Xcodeproj
  class Project
    class UUIDGenerator
      def initialize(project)
        @project = project
        @new_objects_by_uuid = {}
        @paths_by_object = {}
      end

      def generate!
        all_objects = @project.objects
        generate_paths(@project.root_object)
        switch_uuids(all_objects)
        verify_no_duplicates!(all_objects)
        fixup_uuid_references
        @project.instance_variable_set(:@generated_uuids, @project.instance_variable_get(:@available_uuids))
        @project.instance_variable_set(:@objects_by_uuid, @new_objects_by_uuid)
      end

      private

      UUID_ATTRIBUTES = [:remote_global_id_string, :container_portal, :target_proxy].freeze

      def verify_no_duplicates!(all_objects)
        duplicates = all_objects - @new_objects_by_uuid.values
        UserInterface.warn "[Xcodeproj] Generated duplicate UUIDs:\n\n" <<
          duplicates.map { |d| "#{d.isa} -- #{@paths_by_object[d]}" }.join("\n") unless duplicates.empty?
      end

      def fixup_uuid_references
        fixup = ->(object, attr) do
          if object.respond_to?(attr) && link = @project.objects_by_uuid[object.send(attr)]
            object.send(:"#{attr}=", link.uuid)
          end
        end
        @project.objects.each do |object|
          UUID_ATTRIBUTES.each do |attr|
            fixup[object, attr]
          end
        end
      end

      def generate_paths(object, path = '')
        existing = @paths_by_object[object] || path
        @paths_by_object[object] = path.size > existing.size ? path : existing

        object.to_one_attributes.each do |attrb|
          obj = attrb.get_value(object)
          generate_paths(obj, path + '/' << attrb.plist_name) if obj
        end

        object.to_many_attributes.each do |attrb|
          attrb.get_value(object).each do |o|
            generate_paths(o, path + '/' << attrb.plist_name << "/#{path_component_for_object(o)}")
          end
        end

        object.references_by_keys_attributes.each do |attrb|
          attrb.get_value(object).each do |dictionary|
            dictionary.each do |key, value|
              generate_paths(value, path + '/' << attrb.plist_name << "/k:#{key}/#{path_component_for_object(value)}")
            end
          end
        end
      end

      def switch_uuids(objects)
        objects.each do |object|
          next unless path = @paths_by_object[object]
          uuid = uuid_for_path(path)
          object.instance_variable_set(:@uuid, uuid)
          @new_objects_by_uuid[uuid] = object
        end
      end

      def uuid_for_path(path)
        Digest::MD5.hexdigest(path).upcase
      end

      def path_component_for_object(object)
        @path_component_for_object ||= Hash.new do |cache, object|
          component = tree_hash_to_path(object.to_tree_hash)
          component << object.hierarchy_path.to_s if object.respond_to?(:hierarchy_path)
          cache[object] = component
        end
        @path_component_for_object[object]
      end

      def tree_hash_to_path(object, depth = 4)
        return '|' if depth.zero?
        case object
        when Hash
{'fraction_non_alphanumeric': 0.10131147540983607, 'fraction_numerical': 0.0006557377049180328, 'mean_word_length': 2.3838047698280644, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 6, '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': '1996693', 'n_tokens_mistral': 1897, 'n_tokens_neox': 1874, 'n_words': 341}