note
	description: "[
					Objects representing a path, i.e. a way to identify a file or a directory for the
					current underlying platform. A path is made of two components:
					1 - an optional root which can either be:
						a - a drive letter followed by colon on Windows, i.e. "C:" or "C:\"
						b - "/" for UNIX root directory.
						c - "\" for Windows root directory.
						d - "\\server\share" or "\\server\share\" for Microsoft UNC path.
					2 - a sequence of zero or more names.
					
					A path is absolute if it has a root, and on windows if the root is a drive, then it should
					be followed by "\". Otherwise a path is relative.
		
		
					Validity
					========
					
					The current class will not check the validity of filenames. Check your file
					system for your operating system manual for the list of invalid characters.
		
		
					Windows consideration
					=====================
					
					When the root of a Windows path is a drive, be aware of the following behavior:
					1 - "C:filename.txt" refers to the file name "filename.txt" in the current directory 
					    on drive "C:".
					2 - "C:sub\filename.txt" refers to the file name "filename.txt" in a subdirectory "sub"
					    of the current directory on drive "C:".
					3 - "C:\sub\filename.txt" refers to the file name "filename.txt" in a subdirectory "sub"
					    located at the root of the drive "C:".
					
					Both forward and backslashes are accepted, but forward slashes are internally converted
					to backward slashes whenever they are used to construct a path.
					
					On Windows, there is a limit of 259 characters for a path. If you need to create a larger
					path, you can do so by using the following conventions which will let you have paths of
					about 32,767 characters:
					1 - Use \\?\ for non-UNC path and let the rest unchanged.
					2 - Use \\?\UNC\server\share for UNC path and let the rest unchanged.
					The above path cannot be used to specify a relative path.
					
					To know more about Windows paths, read the "Naming Files, Paths, and Namespaces"
					document located at:
					  http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
		
		
					Unicode consideration
					=====================
					
					The PATH class treats strings as sequence of Unicode characters, i.e. an instance of 
					a READABLE_STRING_8 or descendant will be treated as if characters in the range
					128 .. 255 were Unicode code points.
					This contrasts to the FILE/DIRECTORY classes where to preserve backward compatibility, those
					characters are treated as is.
					
					
					Mixed-encoding consideration
					============================
					
					Most operating systems have conventions for paths that are incompatible with Unicode.
					On UNIX, in a sequence of names, each name is just a null-terminated byte sequence, it
					does not follow any specific encoding. Usually the locale setting enables you to see
					the filename the way you expect.
					On Windows, the sequence of names is made of null-terminated UTF-16 code unit sequence. Windows
					does not guarantee that the sequence is actually a valid UTF-16 sequence.
					
					In other words, when there is an invalid UTF-8 encoding on UNIX, or an invalid UTF-16 encoding
					on Windows, the filename is not directly representable as a Unicode string. To make it possible
					to create and store paths in a textually representable form, the query name will create
					an encoded representation that can be then later used in make_from_string to create a PATH
					equivalent to the original path. The encoding is described in UTF_CONVERTER's note clause
					and is a fourth variant of the recommended practice for replacement characters in Unicode
					(see http://www.unicode.org/review/pr-121.html).
		
								
					Immutability
					============
					
					Instances of the current class are immutable.
	]"
	library: "Free implementation of ELKS library"
	status: "See notice at end of class."
	date: "$Date: 2017-03-23 19:18:26 +0000 (Thu, 23 Mar 2017) $"
	revision: "$Revision: 100033 $"

class 
	PATH

inherit
	HASHABLE
		redefine
			out,
			is_equal,
			copy
		end

	COMPARABLE
		redefine
			out,
			is_equal,
			copy
		end

	NATIVE_STRING_HANDLER
		redefine
			out,
			is_equal,
			copy
		end

	DEBUG_OUTPUT
		redefine
			out,
			is_equal,
			copy
		end

create {NATIVE_STRING_HANDLER}
	make_from_pointer


create {PATH}
	make_from_storage,
	make_from_normalized_storage


create 
	make_empty,
	make_current,
	make_from_string,
	make_from_separate

feature {NONE} -- Initialization

	make_empty
			-- Initialize current as an empty path.
		do
			create storage.make_empty
			is_normalized := True
			reset_internal_data
		ensure
			is_empty: is_empty
			is_normalized: is_normalized
		end

	make_current
			-- Initialize current as the symbolic representation of the current working directory
		do
			create storage.make (unit_size)
			storage.append_character ('.')
			if {PLATFORM}.is_windows then
				storage.append_character ('%U')
			end
			is_normalized := True
			reset_internal_data
		ensure
			not_empty: not is_empty
			is_normalized: is_normalized
			is_current: is_current_symbol
		end

	make_from_string (a_path: READABLE_STRING_GENERAL)
			-- Initialize current from a_path treated as a sequence of Unicode characters.
			-- If a_path is trying to represent a mixed-encoded path, then a_path should use
			-- the escaped representation as described in UTF_CONVERTER.
		require
			a_path_not_void: a_path /= Void
		do
			create storage.make (a_path.count * unit_size)
			if not a_path.is_empty then
				internal_append_into (storage, a_path, '%U')
				normalize
			else
				is_normalized := True
			end
			reset_internal_data
		ensure
			not_empty: not a_path.is_empty implies not is_empty
			is_normalized: is_normalized
			roundtrip: True
			roundtrip_with_trailing: True
		end

	make_from_separate (a_path: separate PATH)
			-- Initialize from separate a_path.
		require
			a_path_not_void: a_path /= Void
		do
			create storage.make_from_separate (a_path.storage)
			is_normalized := True
			reset_internal_data
		ensure
			not_empty: not a_path.is_empty implies not is_empty
			is_normalized: is_normalized
		end
	
feature {NONE} -- Internal initialization

	make_from_storage (a_path: STRING_8)
			-- Initialize current from a_path and normalize a_path as it may be coming
			-- from a user provided string or from a C API.
		require
			a_path_not_void: a_path /= Void
		do
			storage := a_path
			normalize
			reset_internal_data
		ensure
			shared: storage = a_path
			is_normalized: is_normalized
		end

	make_from_normalized_storage (a_path: STRING_8)
			-- Initialize current from a_path which has already been normalized.
		require
			a_path_not_void: a_path /= Void
		do
			storage := a_path
			is_normalized := True
			reset_internal_data
		ensure
			shared: storage = a_path
			is_normalized: is_normalized
		end

	make_from_pointer (a_path_pointer: POINTER)
			-- Initialize current from a_path_pointer, a platform system specific encoding of
			-- a path that is null-terminated.
		require
			a_path_pointer_not_null: a_path_pointer /= default_pointer
		local
			l_cstr: C_STRING
			nb: INTEGER_32
		do
			nb := pointer_length_in_bytes (a_path_pointer)
			nb := nb - nb \\ unit_size
			create l_cstr.make_shared_from_pointer_and_count (a_path_pointer, nb)
			storage := l_cstr.substring (1, nb)
			if not storage.is_empty then
				normalize
			end
			reset_internal_data
		end
	
feature -- Status report

	is_current_symbol: BOOLEAN
			-- Is Current a representation of "."?
		do
			if storage.count = unit_size then
				Result := is_character (storage, 1, '.')
			end
		end

	is_parent_symbol: BOOLEAN
			-- Is Current Representation of ".."?
		do
			if storage.count = 2 * unit_size then
				Result := is_character (storage, 1, '.')
				Result := is_character (storage, 1 + unit_size, '.')
			end
		end

	has_root: BOOLEAN
			-- Does current have a root?
		do
			Result := root_end_position /= 0
		ensure
			defintion: Result implies not is_empty
		end

	is_empty: BOOLEAN
			-- Is current empty, i.e. no root and no sequence of names?
		do
			Result := storage.is_empty
		end

	is_relative: BOOLEAN
			-- Is current path relative?
		do
			Result := not is_absolute
		end

	is_absolute: BOOLEAN
			-- Is current path absolute?
		local
			l_root_end_position: INTEGER_32
		do
			l_root_end_position := root_end_position
			if l_root_end_position > 0 then
				if {PLATFORM}.is_windows then
					Result := is_character (storage, l_root_end_position - unit_size + 1, directory_separator)
				else
					Result := True
				end
			end
		end

	is_simple: BOOLEAN
			-- Is current path made of only one name and no root?
			-- I.e. readme.txt, usr or the empty path.
		local
			l_root_pos: like root_end_position
		do
			if storage.is_empty then
				Result := True
			else
				l_root_pos := root_end_position
				if l_root_pos = 0 and not is_empty then
					Result := next_directory_separator (1) = 0
				end
			end
		end

	is_same_file_as (a_path: PATH): BOOLEAN
			-- Does Current and a_path points to the same file on disk? It is different from path equality
			-- as it will take into account symbolic links.
			-- If Current or/and a_path do not exists, it will yield false, otherwise it will compare
			-- the file at the file system level.
		require
			a_path_not_void: a_path /= Void
		local
			l_p1, l_p2: MANAGED_POINTER
		do
			l_p1 := to_pointer
			l_p2 := a_path.to_pointer
			Result := c_same_files (l_p1.item, l_p2.item)
		end

	has_extension (a_ext: READABLE_STRING_GENERAL): BOOLEAN
			-- Does Current has an extension a_ext compared in a case insensitive manner?
		require
			a_ext_not_void: a_ext /= Void
			a_ext_not_empty: not a_ext.is_empty
			a_ext_has_no_dot: not a_ext.has ('.'.to_character_32)
		do
			Result := attached extension as l_ext and then l_ext.is_case_insensitive_equal_general (a_ext)
		end
	
feature -- Access

	root: detachable PATH
			-- Root if any of current path.
		local
			l_pos: INTEGER_32
		do
			l_pos := root_end_position
			if l_pos /= 0 then
				if l_pos = storage.count then
					create Result.make_from_normalized_storage (storage)
				else
					create Result.make_from_normalized_storage (storage.substring (1, l_pos))
				end
			end
		ensure
			has_root_implies_not_void: has_root implies Result /= Void
		end

	parent: PATH
			-- Parent directory if any, otherwise current working path.
			-- The parent of a path consists of root if any, and of each
			-- simple names in the current sequence except for the last.
		local
			l_pos, l_root_end_pos: INTEGER_32
		do
			l_pos := end_position_of_last_directory_separator
			if l_pos = 0 then
				if attached root as l_root then
					check
						is_windows: {PLATFORM}.is_windows
					end
					Result := l_root
				else
					create Result.make_current
				end
			elseif l_pos = unit_size then
				create Result.make_from_normalized_storage (storage.substring (1, unit_size))
			else
				l_root_end_pos := root_end_position
				if l_pos <= l_root_end_pos then
					if l_root_end_pos = storage.count then
						Result := Current
					else
						create Result.make_from_normalized_storage (storage.substring (1, l_root_end_pos))
					end
				else
					create Result.make_from_normalized_storage (storage.substring (1, l_pos - unit_size))
				end
			end
		end

	entry: detachable PATH
			-- Name of file or directory denoted by Current if any.
			-- This is the last name in the current sequence.
		local
			l_pos: INTEGER_32
			l_end_root: INTEGER_32
		do
			l_pos := end_position_of_last_directory_separator
			if l_pos = 0 then
				l_end_root := root_end_position
				if l_end_root > 0 then
					if l_end_root < storage.count then
						create Result.make_from_normalized_storage (storage.substring (l_end_root + 1, storage.count))
					end
				else
					Result := Current
				end
			else
				l_end_root := root_end_position
				if l_pos <= l_end_root then
					if l_end_root < storage.count then
						create Result.make_from_normalized_storage (storage.substring (l_end_root + 1, storage.count))
					end
				else
					create Result.make_from_normalized_storage (storage.substring (l_pos + 1, storage.count))
				end
			end
			if Result /= Void and then (Result.is_empty or Result.is_current_symbol or Result.is_parent_symbol) then
				Result := Void
			end
		ensure
			not_empty: Result /= Void implies not Result.is_empty
		end

	extension: detachable IMMUTABLE_STRING_32
			-- Extension if any of current entry.
		local
			l_name: like name
			l_pos, nb: INTEGER_32
		do
			if attached entry as l_entry then
				l_name := l_entry.name
				nb := l_name.count
				l_pos := l_name.last_index_of ('.'.to_character_32, nb)
				if l_pos /= 0 and l_pos /= nb then
					Result := l_name.shared_substring (l_pos + 1, nb)
				end
			end
		ensure
			not_empty: attached Result implies not Result.is_empty
			no_dot: attached Result implies not Result.has ('.'.to_character_32)
		end

	components: ARRAYED_LIST [PATH]
			-- Sequence of simple paths making up Current, including root if any.
		local
			l_storage: STRING_8
			l_previous_pos, l_pos: INTEGER_32
		do
			create Result.make (10)
			l_pos := root_end_position
			if l_pos > 0 then
				create l_storage.make (l_pos)
				l_storage.append_substring (storage, 1, l_pos)
				Result.extend (create {PATH}.make_from_normalized_storage (l_storage))
				l_pos := l_pos + 1
			else
				l_pos := 1
			end
			if l_pos <= storage.count then
				from
					l_previous_pos := l_pos
					l_pos := next_directory_separator (l_previous_pos)
				until
					l_pos = 0
				loop
					create l_storage.make (l_pos - l_previous_pos)
					l_storage.append_substring (storage, l_previous_pos, l_pos - 1)
					Result.extend (create {PATH}.make_from_normalized_storage (l_storage))
					l_previous_pos := l_pos + unit_size
					l_pos := next_directory_separator (l_previous_pos)
				end
				if l_previous_pos <= storage.count then
					create l_storage.make (storage.count - l_previous_pos)
					l_storage.append_substring (storage, l_previous_pos, storage.count)
					Result.extend (create {PATH}.make_from_normalized_storage (l_storage))
				end
			end
		end

	absolute_path: PATH
			-- Absolute path of Current.
			-- If Current is already absolute, then return Current.
			-- If Current is empty, then return the current working directory.
			-- Otherwise resolve the path in a platform specific way:
			-- * On UNIX, resolve against the current working directory
			-- * On Windows:
			--    a) if current has a drive letter which is not followed by "\"
			--       resolve against the current working directory for that drive letter,
			--       otherwise resolve against the current working directory.
			--    b) if current path starts with "\", not a double "\\", then resolve
			--       against the root of the current working directory (i.e. a drive
			--       letter "C:\" or a UNC path "\\server\share\".)
		do
			Result := absolute_path_in (Env.current_working_path)
		end

	absolute_path_in (a_current_directory: PATH): PATH
			-- Absolute path of Current in the context of a_current_directory.
			-- If Current is already absolute, then return Current.
			-- If Current is empty, then return a_current_directory.
			-- Otherwise resolve the path in a platform specific way:
			-- * On UNIX, resolve against a_current_directory
			-- * On Windows:
			--    a) if current has a drive letter which is not followed by "\"
			--       resolve against a_current_directory for that drive letter,
			--       otherwise resolve against a_current_directory.
			--    b) if current path starts with "\", not a double "\\", then resolve
			--       against the root of `a_current_directory; (i.e. a drive
			--       letter "C:\" or a UNC path "\\server\share\".)
		require
			a_current_directory_not_void: a_current_directory /= Void
			a_current_directory_absolute: a_current_directory.is_absolute
		do
			if storage.is_empty then
				Result := a_current_directory
			else
				if is_absolute then
					Result := Current
				else
					if {PLATFORM}.is_windows then
						if attached root as l_root then
							if (l_root.storage.count = 4 and a_current_directory.storage.count >= 4) and then (l_root.storage.item (1) = a_current_directory.storage.item (1) and l_root.storage.item (3) = a_current_directory.storage.item (3)) then
								Result := a_current_directory.twin
							else
								Result := l_root
							end
							internal_path_append_substring_into (Result.storage, storage, l_root.storage.count + 1, storage.count, directory_separator)
						else
							if is_character (storage, 1, Windows_separator) then
								Result := a_current_directory.twin
								if attached Result.root as l_root then
									Result := l_root
								end
							else
								Result := a_current_directory.twin
							end
							internal_path_append_into (Result.storage, storage, directory_separator)
						end
					else
						Result := a_current_directory.twin
						internal_path_append_into (Result.storage, storage, directory_separator)
					end
					Result.reset_internal_data
				end
			end
		ensure
			has_root: Result.has_root
		end

	canonical_path: PATH
			-- Canonical path of Current.
			-- Similar to absolute_path except that sequences containing "." or ".." are
			-- resolved.
		local
			l_components: like components
			l_absolute_path: like absolute_path
			l_storage: like storage
		do
			l_absolute_path := absolute_path
			if attached l_absolute_path.root as l_root then
				create l_storage.make (l_absolute_path.storage.count)
				l_components := l_absolute_path.components
				check
					l_components_has_root: l_components.count >= 1
					l_components_first_is_root: l_components.first.same_as (l_root)
				end
				from
					l_components.start
					internal_path_append_into (l_storage, l_components.item.storage, directory_separator)
					l_components.remove
				until
					l_components.after
				loop
					if l_components.item.is_current_symbol then
						l_components.remove
					elseif l_components.item.is_parent_symbol then
						if not l_components.isfirst then
							l_components.back
							l_components.remove
						end
						l_components.remove
					else
						l_components.forth
					end
				end
				across
					l_components as l_component
				loop
					internal_path_append_into (l_storage, l_component.item.storage, directory_separator)
				end
				create Result.make_from_normalized_storage (l_storage)
			else
				check
						False
				end
				Result := l_absolute_path
			end
		end

	hash_code: INTEGER_32
			-- Hash code value
		do
			if {OPERATING_ENVIRONMENT}.case_sensitive_path_names then
				Result := storage.hash_code
			else
				Result := name.case_insensitive_hash_code
			end
		end

	native_string: NATIVE_STRING
			-- Convert current into an instance of NATIVE_STRING
		do
			create Result.make_from_raw_string (storage)
		ensure
			set: Result.raw_string.same_string (storage)
		end

	Unix_separator: CHARACTER_8 = '/'

	Windows_separator: CHARACTER_8 = '\'
			-- Platform specific directory separator.

	directory_separator: CHARACTER_8
			-- Default directory separator for the current platform.
		do
			if {PLATFORM}.is_windows then
				Result := Windows_separator
			else
				Result := Unix_separator
			end
		end
	
feature -- Status setting

	extended (a_name: READABLE_STRING_GENERAL): PATH
			-- New path instance of current extended with path a_name.
			-- If current is not empty, then a_name cannot have a root.
			-- A directory separator is added between two entries except
			-- 1 - a_name starts with a directory separator (i.e. it has a root)
			-- 2 - if current is empty or is just a root.
			-- Note that a_name can be an encoding of a mixed-encoding simple name and it will
			-- be decoded accordingly (see note clause for the class for more details.)
		require
			a_name_not_void: a_name /= Void
			a_name_not_empty: not a_name.is_empty
			a_name_has_no_root: not is_empty implies not (create {PATH}.make_from_string (a_name)).has_root
		local
			l_storage: like storage
		do
			create l_storage.make (storage.count + a_name.count * unit_size + unit_size)
			l_storage.append (storage)
			if l_storage.count > 0 and root_end_position = l_storage.count then
				internal_append_into (l_storage, a_name, '%U')
			else
				internal_append_into (l_storage, a_name, directory_separator)
			end
			create Result.make_from_storage (l_storage)
		ensure
			associated_path_of_name: attached (create {PATH}.make_from_string (a_name)) as l_path
			not_empty: not Result.is_empty
			extended_with_only_empty_or_root: (same_as (root) or is_empty) implies Result.name.same_string (name + l_path.name)
			extended_with_more_than_root_or_not_empty: (not same_as (root) and not is_empty) implies Result.name.same_string (name + Directory_separator_string + l_path.name)
		end

	extended_path alias "+" (a_path: PATH): PATH
			-- New path instance of current extended with path a_path.
			-- If current is not empty, then a_path cannot have a root.
			-- A directory separator is added between two entries except
			-- 1 - a_path starts with a directory separator (i.e. it has a root)
			-- 2 - if current is empty or is just a root.
		require
			a_path_not_void: a_path /= Void
			a_path_not_empty: not a_path.is_empty
			a_path_has_no_root: not is_empty implies not a_path.has_root
		local
			l_storage: like storage
		do
			create l_storage.make (storage.count + a_path.storage.count + unit_size)
			l_storage.append (storage)
			if l_storage.count > 0 and root_end_position = l_storage.count then
				internal_path_append_into (l_storage, a_path.storage, '%U')
			else
				internal_path_append_into (l_storage, a_path.storage, directory_separator)
			end
			create Result.make_from_normalized_storage (l_storage)
		ensure
			not_empty: not Result.is_empty
			extended_with_only_empty_or_root: (same_as (root) or is_empty) implies Result.name.same_string (name + a_path.name)
			extended_with_more_than_root_or_not_empty: (not same_as (root) and not is_empty) implies Result.name.same_string (name + Directory_separator_string + a_path.name)
		end

	appended (a_extra: READABLE_STRING_GENERAL): PATH
			-- New path instance of current where entry is appended with a_extra without
			-- adding a directory separator.
			-- For example if Current path is "C:" and a_path is "path\file.txt", it will yield
			-- "C:path\file.txt".
			-- Note that a_extra can be an encoding of a mixed-encoding simple name and it will
			-- be decoded accordingly (see note clause for the class for more details.)
		require
			a_extra_not_void: a_extra /= Void
			a_extra_not_empty: not a_extra.is_empty
		local
			l_storage: like storage
		do
			create l_storage.make (storage.count + a_extra.count * unit_size)
			l_storage.append (storage)
			internal_append_into (l_storage, a_extra, '%U')
			create Result.make_from_storage (l_storage)
		ensure
			not_empty: not Result.is_empty
			appended: Result.name.same_string (name + (create {PATH}.make_from_string (a_extra)).name)
		end

	appended_with_extension (a_ext: READABLE_STRING_GENERAL): PATH
			-- New path instance of current where entry is extended with a dot followed by a_ext.
			-- If Current already has a dot, no dot is added.
			-- Note that a_ext can be an encoding of a mixed-encoding simple name and it will
			-- be decoded accordingly (see note clause for the class for more details.)
		require
			a_ext_not_void: a_ext /= Void
			a_ext_not_empty: not a_ext.is_empty
			a_ext_has_no_dot: not a_ext.has ('.'.to_character_32)
			a_ext_has_no_directory_separator: not a_ext.has (Windows_separator.to_character_32) and not a_ext.has (Unix_separator.to_character_32)
			has_entry: entry /= Void
		local
			l_storage: like storage
		do
			create l_storage.make (storage.count + a_ext.count * unit_size + unit_size)
			l_storage.append (storage)
			internal_append_into (l_storage, a_ext, '.')
			create Result.make_from_normalized_storage (l_storage)
		ensure
			not_empty: not Result.is_empty
			extension_set: attached Result.extension as l_ext and then l_ext.same_string_general (a_ext)
			components_stable: Result.components.count = components.count
		end
	
feature -- Comparison

	same_as (other: detachable PATH): BOOLEAN
			-- Is Current the same path as other?
			-- Note that no canonicalization is being performed to compare paths,
			-- paths are compared using the OS-specific convention for letter case.
		do
			if other = Void then
			elseif other = Current then
				Result := True
			else
				if {OPERATING_ENVIRONMENT}.case_sensitive_path_names then
					Result := is_case_sensitive_equal (other)
				else
					Result := is_case_insensitive_equal (other)
				end
			end
		end

	is_less alias "<" (other: like Current): BOOLEAN
			-- Is current object less than other?
		do
			if {OPERATING_ENVIRONMENT}.case_sensitive_path_names then
				Result := storage < other.storage
			else
				Result := name.as_lower < other.name.as_lower
			end
		end

	is_equal (other: like Current): BOOLEAN
			-- Is other attached to an object considered
			-- equal to current object?
		do
			Result := same_as (other)
		end

	is_case_sensitive_equal (other: PATH): BOOLEAN
			-- Compare path and paying attention to case.
		require
			other_not_void: other /= Void
		do
			if other = Current then
				Result := True
			else
				Result := storage.is_equal (other.storage)
			end
		end

	is_case_insensitive_equal (other: PATH): BOOLEAN
			-- Compare path without paying attention to case. If the path is containing some mixed-encoding
			-- we might ignore many characters when doing the case comparison.
		require
			other_not_void: other /= Void
		do
			if other = Current then
				Result := True
			else
				Result := name.is_case_insensitive_equal (other.name)
			end
		end
	
feature -- Duplication

	copy (other: like Current)
			-- Update current object using fields of object attached
			-- to other, so as to yield equal objects.
		do
			if other /= Current then
				standard_copy (other)
				storage := other.storage.twin
			end
		end
	
feature -- Output

	out: STRING_8
			-- ASCII representation of the underlying filename if representable,
			-- otherwise a UTF-8 encoded version.
			-- Use utf_8_name to have a printable representation whose format is not going
			-- to be changed in the future.
		do
			Result := utf_8_name
		end

	utf_8_name: STRING_8
			-- UTF-8 representation of the underlying filename.
		local
			u: UTF_CONVERTER
		do
			Result := u.utf_32_string_to_utf_8_string_8 (name)
		end

	name: IMMUTABLE_STRING_32
			-- If current is representable in Unicode, the Unicode representation.
			-- Otherwise all non-valid sequences for the current platform in the path are escaped
			-- as mentioned in the note clause of the class.
			-- To ensure roundtrip, you cannot use name directly to create a FILE, you have to
			-- create a PATH instance using make_from_string before passing it to the creation
			-- procedure of FILE taking an instance of PATH.
		local
			u: UTF_CONVERTER
		do
			if attached internal_name as l_name then
				Result := l_name
			else
				if {PLATFORM}.is_windows then
					create Result.make_from_string (u.utf_16le_string_8_to_escaped_string_32 (storage))
				else
					create Result.make_from_string (u.utf_8_string_8_to_escaped_string_32 (storage))
				end
				internal_name := Result
			end
		ensure
			roundtrip: same_as (create {PATH}.make_from_string (Result))
		end
	
feature {NONE} -- Output

	debug_output: READABLE_STRING_32
			-- String that should be displayed in debugger to represent Current.
		do
			Result := name
		end
	
feature {NATIVE_STRING_HANDLER} -- Access

	to_pointer: MANAGED_POINTER
			-- Platform specific representation of Current.
		local
			l_cstr: C_STRING
		do
			create l_cstr.make_empty (storage.count + unit_size - 1)
			l_cstr.set_string (storage)
			Result := l_cstr.managed_data
		end
	
feature {PATH} -- Implementation

	is_normalized: BOOLEAN
			-- Has current string be normalized?

	storage: STRING_8
			-- Internal storage for Current.
			-- On UNIX, it is a binary sequence encoded in UTF-8 by default.
			-- On Windows, it is a binary sequence encoded in UTF-16LE by default.

	unit_size: INTEGER_32
			-- Size in bytes of a unit for storage.
		do
			if {PLATFORM}.is_windows then
				Result := 2
			else
				Result := 1
			end
		end

	reset_internal_data
			-- Reset the private cache data.
		do
			internal_name := Void
		end

	normalize
			-- Normalize current with respect to directory separators:
			-- 1 - On Windows, replace all / by \.
			-- 2 - Remove trailing directory separator unless this is a root for which
			--     we will keep just one. This is the case of:
			--      * C:\
			--      * \\server\share\
			--      * \\?\C:\
			--      * /
			--     where removing the trailing directory separator would transform
			--     current path from being absolute to be relative.
			-- 3 - Remove redundant directory separator in a path except on Windows for
			--     the starting \\ in a UNC path. (i.e. /foo////bar -> /foo/bar, and
			--     \\server\share\\\\foo\\\bar -> \\server\share\foo\bar.
			-- 4 - If a path is just made of directory separator, it become that directory separator.
		require
			not_normalized: not is_normalized
			storage_not_empty: storage.count >= unit_size
		local
			l_storage: like storage
			i, j, nb: INTEGER_32
			l_root_pos: INTEGER_32
			l_in_separator, l_copy_character, l_has_unc: BOOLEAN
		do
			l_storage := storage
			nb := l_storage.count
			if {PLATFORM}.is_windows then
				i := 1
				if nb >= Min_unc_path_count then
					if (is_character (l_storage, 1, Unix_separator) and (is_character (l_storage, 3, Unix_separator) or is_character (l_storage, 3, Windows_separator))) or (is_character (l_storage, 1, Windows_separator) and (is_character (l_storage, 3, Windows_separator) or is_character (l_storage, 3, Unix_separator))) then
						if not is_character (l_storage, 5, Unix_separator) and not is_character (l_storage, 5, Windows_separator) then
							i := 5
							l_has_unc := True
						end
					end
				end
				from
					j := i
					l_copy_character := True
				until
					i > nb
				loop
					if l_in_separator then
						l_copy_character := not is_character (l_storage, i, Windows_separator) and not is_character (l_storage, i, Unix_separator)
						l_in_separator := not l_copy_character
					else
						if is_character (l_storage, i, Unix_separator) then
							l_storage.put (Windows_separator, i)
							l_in_separator := True
						else
							l_in_separator := is_character (l_storage, i, Windows_separator)
						end
					end
					if l_copy_character then
						if i /= j then
							l_storage.put (l_storage.item (i), j)
							l_storage.put (l_storage.item (i + 1), j + 1)
						end
						j := j + 2
					end
					i := i + 2
				end
			else
				from
					i := 1
					j := i
					l_copy_character := True
				until
					i > nb
				loop
					if l_in_separator then
						l_copy_character := not is_character (l_storage, i, Unix_separator)
						l_in_separator := not l_copy_character
					else
						l_in_separator := is_character (l_storage, i, Unix_separator)
					end
					if l_copy_character then
						if i /= j then
							l_storage.put (l_storage.item (i), j)
						end
						j := j + 1
					end
					i := i + 1
				end
			end
			if i /= j then
				l_storage.keep_head (j - 1)
			end
			is_normalized := True
			if l_has_unc then
				l_root_pos := root_end_position
				if l_root_pos = 0 then
					l_storage.remove_head (unit_size)
				end
			end
			if is_character (l_storage, l_storage.count - unit_size + 1, directory_separator) and then root_end_position < l_storage.count then
				l_storage.remove_tail (unit_size)
			end
		ensure
			is_normalized: is_normalized
		end
	
feature {NONE} -- Implementation

	internal_name: detachable IMMUTABLE_STRING_32
			-- Cache for name.

	Platform: PLATFORM
			-- Access underlying platform info, used to satisfy invariant below.
		once
			create Result
		end

	Env: EXECUTION_ENVIRONMENT
			-- Access to underlying execution environment.
		once
			create Result
		end

	root_end_position: INTEGER_32
			-- Position of the last character of root as a full unit if any, 0 otherwise.
		require
			is_normalized: is_normalized
		local
			l_drive_letter: CHARACTER_8
			l_pos: INTEGER_32
		do
			if not storage.is_empty then
				if {PLATFORM}.is_windows then
					if storage.count = unit_size and then is_character (storage, 1, Windows_separator) then
						Result := unit_size
					elseif storage.count >= 4 and then (storage.item (2) = '%U' and storage.item (4) = '%U') then
						l_drive_letter := storage.item (1).as_lower
						if l_drive_letter >= 'a' and l_drive_letter <= 'z' and storage.item (3) = ':' then
							if storage.count >= 3 * unit_size and then is_character (storage, 5, Windows_separator) then
								Result := 6
							else
								Result := 4
							end
						elseif l_drive_letter = Windows_separator and storage.item (3) /= Windows_separator then
							Result := unit_size
						elseif storage.count >= Min_unc_path_count and l_drive_letter = Windows_separator and storage.item (3) = Windows_separator and storage.item (5) /= Windows_separator then
							l_pos := next_directory_separator (7)
							if l_pos > 0 and l_pos + unit_size <= storage.count then
								l_pos := next_directory_separator (l_pos + unit_size)
								if l_pos > 0 then
									Result := l_pos + 1
								else
									Result := storage.count
								end
							end
						end
					end
				else
					if storage.item (1) = Unix_separator then
						Result := 1
					end
				end
			end
		ensure
			non_negative: Result >= 0
			not_too_big: Result <= storage.count
			valid_for_windows: {PLATFORM}.is_windows implies Result \\ unit_size = 0
		end

	end_position_of_last_directory_separator: INTEGER_32
			-- Position of the last directory separator in Current, not including the trailing ones if any, 0 if none.
		require
			is_normalized: is_normalized
		do
			if not storage.is_empty then
				Result := storage.count - unit_size + 1
				if Result >= 1 then
					from
					until
						Result < 1 or is_character (storage, Result, directory_separator)
					loop
						Result := Result - unit_size
					end
				end
				if Result < 0 then
					Result := 0
				else
					Result := Result + unit_size - 1
				end
			end
		ensure
			non_negative: Result >= 0
			not_too_big: Result <= storage.count
			valid_for_windows: {PLATFORM}.is_windows implies Result \\ unit_size = 0
		end

	next_directory_separator (a_starting_pos: INTEGER_32): INTEGER_32
			-- Starting at a position a_starting_pos find the position in storage of the
			-- next directory separator, or 0 otherwise.
		require
			a_starting_pos_valid: a_starting_pos >= 1 and a_starting_pos <= storage.count
			a_starting_pos_is_well_positionned: {PLATFORM}.is_windows implies a_starting_pos \\ unit_size = 1
		local
			nb: INTEGER_32
			l_step: like unit_size
			l_sep: like directory_separator
			l_storage: like storage
		do
			from
				l_step := unit_size
				l_sep := directory_separator
				Result := a_starting_pos
				l_storage := storage
				nb := l_storage.count
			until
				Result < 1 or Result > nb or is_character (l_storage, Result, l_sep)
			loop
				Result := Result + l_step
			end
			if Result > nb then
				Result := 0
			end
		ensure
			valid_position: Result >= 0 and Result <= storage.count
			well_positionned: {PLATFORM}.is_windows implies ((Result = 0) or (Result \\ unit_size = 1))
			has_separator: Result > 0 implies is_character (storage, Result, directory_separator)
		end

	internal_append_into (a_storage: STRING_8; other: READABLE_STRING_GENERAL; a_separator: CHARACTER_8)
			-- Append a_separator if different from '%U' and not already present as last character
			-- in a_storage, and then other to Current.
		require
			other_not_void: other /= Void
			other_not_empty: not other.is_empty
		local
			u: UTF_CONVERTER
		do
			if not other.is_empty then
				if a_separator /= '%U' and not a_storage.is_empty and then not is_character (a_storage, a_storage.count - unit_size + 1, a_separator) and other.item (1) /= Unix_separator.to_character_32 then
					if {PLATFORM}.is_windows then
						if other.item (1) /= Windows_separator.to_character_32 then
							a_storage.append_character (a_separator)
							a_storage.append_character ('%U')
						end
					else
						a_storage.append_character (a_separator)
					end
				end
				if {PLATFORM}.is_windows then
					u.escaped_utf_32_string_into_utf_16le_string_8 (other, a_storage)
				else
					u.escaped_utf_32_string_into_utf_8_string_8 (other, a_storage)
				end
			end
		end

	internal_path_append_into (a_storage, other: STRING_8; a_separator: CHARACTER_8)
			-- Append a_separator if other than '%U' and not already present as last character
			-- of a_storage, and then other to a_storage.
		require
			other_not_void: other /= Void
			other_not_empty: not other.is_empty
		do
			if a_separator /= '%U' and not a_storage.is_empty and then not is_character (a_storage, a_storage.count - unit_size + 1, a_separator) and not is_character (other, 1, a_separator) then
				a_storage.append_character (a_separator)
				if {PLATFORM}.is_windows then
					a_storage.append_character ('%U')
				end
			end
			a_storage.append (other)
		end

	internal_path_append_substring_into (a_storage, other: STRING_8; other_start_index, other_end_index: INTEGER_32; a_separator: CHARACTER_8)
			-- Append a_separator if other than '%U' and not already present as last character
			-- of a_storage.substring (other_start_index, other_end_index), and then other to a_storage.
		require
			other_not_void: other /= Void
			other_not_empty: not other.is_empty
			other_has_not_trailing_directory_separator: not is_character (other, other.count - unit_size + 1, directory_separator)
		do
			if a_separator /= '%U' and not a_storage.is_empty and then not is_character (a_storage, a_storage.count - unit_size + 1, a_separator) and not is_character (other, other_start_index, a_separator) then
				a_storage.append_character (a_separator)
				if {PLATFORM}.is_windows then
					a_storage.append_character ('%U')
				end
			end
			a_storage.append_substring (other, other_start_index, other_end_index)
		end

	is_character (a_storage: like storage; a_pos: INTEGER_32; a_char: CHARACTER_8): BOOLEAN
			-- Is a_char appearing at position a_pos (in bytes) in a_storage.
		require
			a_pos_valid: a_storage.valid_index (a_pos)
			a_pos_valid_for_windows: {PLATFORM}.is_windows implies a_storage.valid_index (a_pos + 1)
			a_pos_odd_for_windows: {PLATFORM}.is_windows implies (a_pos \\ unit_size) = 1
			a_storage_count_valid_for_windows: {PLATFORM}.is_windows implies (a_storage.count \\ unit_size) = 0
		do
			if {PLATFORM}.is_windows then
				Result := a_storage.item (a_pos) = a_char and then a_storage.item (a_pos + 1) = '%U'
			else
				Result := a_storage.item (a_pos) = a_char
			end
		ensure
			definition: Result = ((a_storage.item (a_pos) = a_char) and then ({PLATFORM}.is_windows implies a_storage.item (a_pos + 1) = '%U'))
		end

	Directory_separator_string: STRING_32
			-- Default directory separator for the current platform.
		once
			if {PLATFORM}.is_windows then
				Result := {STRING_32}"\"
			else
				Result := {STRING_32}"/"
			end
		end

	Min_unc_path_count: INTEGER_32 = 10
			-- Number of characters in storage to make up a valid UNC path: \\a\c whic is 5 Unicode characters, thus 10 bytes.
	
feature {NONE} -- Externals

	c_same_files (a_path1, a_path2: POINTER): BOOLEAN
			-- Do C paths a_path1 and a_path2 represent the same file?
		require
			a_path1_not_null: a_path1 /= default_pointer
			a_path2_not_null: a_path2 /= default_pointer
		external
			"C inline use %"eif_eiffel.h%""
		alias
			"[
				EIF_BOOLEAN Result = EIF_FALSE;
				#ifdef EIF_WINDOWS
						/* To check this, we use `CreateFileW' to open both file, and then using the information
						 * returned by `GetFileInformationByHandle' we can check whether or not they are indeed
						 * the same.
						 * Note: it is important to use the W version of CreateFileW because arguments
						 * are Unicode, not ASCII. */
					BY_HANDLE_FILE_INFORMATION l_path1_info, l_path2_info;
					HANDLE l_path2_file = CreateFileW ((LPCWSTR) $a_path2, GENERIC_READ, FILE_SHARE_READ, NULL,
						OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
					HANDLE l_path1_file = CreateFileW ((LPCWSTR) $a_path1, GENERIC_READ, FILE_SHARE_READ, NULL,
							OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
				
					if ((l_path2_file == INVALID_HANDLE_VALUE) || (l_path1_file == INVALID_HANDLE_VALUE)) {
							/* We do not need the handles anymore, simply close them. Since Microsoft
							 * API accepts INVALID_HANDLE_VALUE we don't check the validity of arguments. */
						CloseHandle(l_path2_file);
						CloseHandle(l_path1_file);
					} else {
						BOOL success = GetFileInformationByHandle (l_path2_file, &l_path2_info);
						success = success && GetFileInformationByHandle (l_path1_file, &l_path1_info);
							/* We do not need the handles anymore, simply close them. */
						CloseHandle(l_path2_file);
						CloseHandle(l_path1_file);
						if (success) {
								/* Check that `path2' and `path1' do not represent the same file. */
							if
								((l_path2_info.dwVolumeSerialNumber == l_path1_info.dwVolumeSerialNumber) &&
								(l_path2_info.nFileIndexLow == l_path1_info.nFileIndexLow) &&
								(l_path2_info.nFileIndexHigh == l_path1_info.nFileIndexHigh))
							{
								Result = EIF_TRUE;
							}
						}
					}
				#else
					struct stat buf1, buf2;
					int status;
					#ifdef HAS_LSTAT
						status = lstat($a_path1, &buf1);
						if (status == 0) {
								/* We found a file, now let's check if it is not a symbolic link. If it is, we use `stat'
							 	 * to ensure the validity of the link. */
							if ((buf1.st_mode & S_IFLNK) == S_IFLNK) {
								status = stat ($a_path1, &buf1);
							}
						}
						
						if (status == 0) {
							status = lstat($a_path2, &buf2);
							if (status == 0) {
									/* We found a file, now let's check if it is not a symbolic link. If it is, we use `stat'
								 	 * to ensure the validity of the link. */
								if ((buf2.st_mode & S_IFLNK) == S_IFLNK) {
									status = stat ($a_path2, &buf2);
								}
							}
						}
					#else
						status = stat ($a_path1, &buf1);
						if (status == 0) {
							status = stat ($a_path2, &buf2);
						}
					#endif
					if (status == 0) {
							/* Both files are present, check they represent the same one. */
						if ((buf1.st_dev == buf2.st_dev) && (buf1.st_ino == buf2.st_ino)) {
							Result = EIF_TRUE;
						}
					}
				#endif
				return Result;
			]"
		end
	
invariant
	little_endian_windows: {PLATFORM}.is_windows implies Platform.Is_little_endian
	even_count_on_windows: {PLATFORM}.is_windows implies storage.count \\ unit_size = 0
	no_forward_slash_on_windows: {PLATFORM}.is_windows implies not storage.has_substring ("/%U")

note
	copyright: "Copyright (c) 1984-2017, Eiffel Software and others"
	license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
	source: "[
		Eiffel Software
		5949 Hollister Ave., Goleta, CA 93117 USA
		Telephone 805-685-1006, Fax 805-685-6869
		Website http://www.eiffel.com
		Customer support http://support.eiffel.com
	]"

end -- class PATH

Generated by ISE EiffelStudio