class EL_PATH_PARENT

(source code)

description

Implementation of EL_PATH for parent_path, status properties, comparison and hashing

note
	description: "[
		Implementation of ${EL_PATH} for `parent_path', status properties, comparison and hashing
	]"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-11-06 10:31:08 GMT (Wednesday 6th November 2024)"
	revision: "13"

deferred class
	EL_PATH_PARENT

inherit
	COMPARABLE
		redefine
			is_equal
		end

	HASHABLE undefine is_equal end

	EL_PATH_BUFFER_ROUTINES
		export
			{EL_PATH_PARENT} temporary_copy, temporary_path
		end

	EL_PATH_CONSTANTS
		export
			{NONE} all
			{ANY} Separator
		end

	DEBUG_OUTPUT undefine is_equal end

	EL_MODULE_DIRECTORY; EL_MODULE_FILE_SYSTEM; EL_MODULE_FORMAT

	EL_READABLE_STRING_GENERAL_ROUTINES_IMP
		export
			{NONE} all
		end

	EL_SHARED_ZSTRING_BUFFER_POOL

	EL_SHARED_PATH_MANAGER; EL_SHARED_WORD

	EL_ZSTRING_CONSTANTS

feature -- Access

	hash_code: INTEGER
			-- Hash code value
		local
			i: INTEGER
		do
			Result := internal_hash_code
			if Result = 0 then
				from i := 1 until i > part_count loop
					Result := Result + part_string (i).hash_code
					i := i + 1
				end
				Result := Result.abs
				internal_hash_code := Result
			end
		end

	parent_string (keep_ref: BOOLEAN): ZSTRING
		do
			if has_volume then
				Result := shared_drive + parent_path

			elseif keep_ref then
				create Result.make_from_other (parent_path)
			else
				Result := parent_path
			end
		end

	volume: CHARACTER_8
		-- volume letter for Windows paths

feature -- Status Query

	exists: BOOLEAN
		do
			Result := not is_empty and then File_system.path_exists (current_path)
		end

	has_parent: BOOLEAN
		do
			if is_absolute then
				if base.count > 0 and then parent_path.count >= 1 then
					Result := parent_path.ends_with_character (Separator)
				end
			else
				Result := parent_path.ends_with_character (Separator)
			end
		end

	has_step (a_step: READABLE_STRING_GENERAL): BOOLEAN
			-- true if path has directory step
		local
			step: ZSTRING; pos_left_separator, pos_right_separator: INTEGER
		do
			if attached String_pool.borrowed_item as borrowed then
				step := borrowed.to_same (a_step)
				pos_left_separator := parent_path.substring_index (step, 1) - 1
				pos_right_separator := pos_left_separator + step.count + 1
				if 0 <= pos_left_separator and pos_right_separator <= parent_path.count then
					if parent_path [pos_right_separator] = Separator then
						Result := pos_left_separator > 0 implies parent_path [pos_left_separator] = Separator
					end
				end
				borrowed.return
			end
		end

	has_volume: BOOLEAN
		do
			Result := volume.code > 0
		end

	is_absolute: BOOLEAN
		-- `True' if `Current' path is absolute
		do
			Result := parent_path.starts_with_character (Separator)
		end

	is_directory: BOOLEAN
		deferred
		end

	is_empty: BOOLEAN
		do
			Result := parent_path.is_empty and base.is_empty
		end

	is_expandable: BOOLEAN
		-- `True' if `base' or `parent' contain what maybe expandable variables
		do
			Result := has_expansion_variable (parent_path) or else has_expansion_variable (base)
		end

	is_file: BOOLEAN
		do
			Result := not is_directory
		end

	is_uri: BOOLEAN
		do
		end

	is_valid_ntfs: BOOLEAN
		local
			ntfs: EL_NT_FILE_SYSTEM_ROUTINES
		do
			Result := ntfs.is_valid (parent_path, True) and then ntfs.is_valid (base, False)
		end

feature -- Comparison

	is_equal (other: like Current): BOOLEAN
			--
		do
			if volume = other.volume then
				Result := base.is_equal (other.base) and parent_path.is_equal (other.parent_path)
			end
		end

	is_less alias "<" (other: like Current): BOOLEAN
		-- Is current object less than `other'?
		local
			one_has_volume, compare_parent_and_base: BOOLEAN
			v, o_v: CHARACTER; l_parent, o_parent: ZSTRING
		do
			v := volume; o_v := other.volume
			if v = o_v then
				compare_parent_and_base := True

			elseif v.code > 0 and then o_v.code > 0 then
				Result := v < o_v
			else
				one_has_volume := True
				compare_parent_and_base := True
			end
			if compare_parent_and_base then
				if one_has_volume then
					if has_volume then
						l_parent := parent_string (False); o_parent := other.parent_path
					else
						l_parent := parent_path; o_parent := other.parent_string (False)
					end
				else
					l_parent := parent_path; o_parent := other.parent_path
				end
				if l_parent ~ o_parent then
					Result := base < other.base
				else
					Result := l_parent < o_parent
				end
			end
		end

feature -- Contract Support

	invalid_ntfs_character (c: CHARACTER): BOOLEAN
		local
			ntfs: EL_NT_FILE_SYSTEM_ROUTINES
		do
			Result := ntfs.invalid_ntfs_character (c)
		end

feature -- Element change

	set_parent (dir_path: EL_DIR_PATH)
		local
			l_path: ZSTRING
		do
			volume := dir_path.volume
			l_path := dir_path.temporary_path
			if has_volume then
				l_path := dir_path.temporary_copy (l_path, 3)
			end
			set_shared_parent_path (l_path)
		end

	set_parent_path (a_parent: READABLE_STRING_GENERAL)
		do
			if attached temporary_copy (a_parent, set_volume_from_string (a_parent)) as l_path then
				set_shared_parent_path (temp_normalized (l_path))
			end
		end

	set_path (a_path: READABLE_STRING_GENERAL)
		-- set `parent_path' and `base' from `a_path' string
		do
			make (a_path)
		end

	set_volume (a_volume: CHARACTER)
		do
			volume := a_volume
		end

feature {NONE} -- Implementation

	has_expansion_variable (a_path: ZSTRING): BOOLEAN
		-- a step contains what might be an expandable variable
		local
			pos_dollor: INTEGER
		do
			pos_dollor := a_path.index_of ('$', 1)
			Result := pos_dollor > 0 and then (pos_dollor = 1 or else a_path [pos_dollor - 1] = Separator)
		end

	part_count: INTEGER
		-- count of string components
		-- (5 in the case of URI paths)
		do
			Result := 3 -- `shared_drive', `parent_path', `base'
		end

	part_string (index: INTEGER): READABLE_STRING_GENERAL
		require
			valid_index: 1 <= index and index <= part_count
		do
			inspect index
				when 1 then
					Result := shared_drive

				when 2 then
					Result := parent_path
			else
				Result := base
			end
		end

	reset_hash
		do
			internal_hash_code := 0
		end

	set_shared_parent_path (a_parent: ZSTRING)
		local
			l_path: ZSTRING; last_index: INTEGER
		do
			last_index := a_parent.count
			inspect last_index
				when 0 then
					parent_path := Empty_path
			else
				l_path := a_parent
				if l_path [last_index] /= Separator then
					if l_path /= Temp_path then
						l_path := empty_temp_path
						l_path.append (a_parent)
					end
					l_path.append_character (Separator)
				end
				Parent_set.put_copy (l_path)
				parent_path := Parent_set.found_item
			end
			internal_hash_code := 0
		end

	set_volume_from_string (a_path: READABLE_STRING_GENERAL): INTEGER
		-- `Result' is index to first character of `a_path' skipping any volume drive characters
		-- like "C:"
		local
			nt: EL_NT_FILE_SYSTEM_ROUTINES
		do
			if {PLATFORM}.is_windows and then nt.has_volume (a_path) then
				Result := 3; volume := a_path [1].to_character_8
			else
				Result := 1; volume := '%U'
			end
		end

	shared_drive: ZSTRING
		do
			if volume.code > 0 then
				Result := Drive
				Result [1] := volume
			else
				Result := Empty_string
			end
		end

	normalized_copy (path: READABLE_STRING_GENERAL): READABLE_STRING_GENERAL
		do
			if {PLATFORM}.is_windows and then path.has ('/') then
				Result := temp_normalized (path).twin
			else
				Result := path
			end
		end

	temp_normalized (path: READABLE_STRING_GENERAL): ZSTRING
		-- temporary path string normalized for platform
		do
			Result := temporary_copy (path, 1)
			if {PLATFORM}.is_windows then
				if is_uri then
					Result.replace_character (Windows_separator, Unix_separator)
				else
					Result.replace_character (Unix_separator, Windows_separator)
				end
			end
		end

feature {EL_PATH_PARENT} -- Internal attributes

	internal_hash_code: INTEGER

	parent_path: ZSTRING

feature {EL_PATH_PARENT} -- Deferred implementation

	base: ZSTRING
		deferred
		end

	current_path: EL_PATH
		deferred
		end

	make (a_path: READABLE_STRING_GENERAL)
		deferred
		end

feature {NONE} -- Constants

	Parent_set: EL_HASH_SET [ZSTRING]
		-- cached set of all `parent_path' strings
		once
			create Result.make_equal (100)
		end

end