note
	description: "project.eif or precompile.eif file for an eiffel project."
	legal: "See notice at end of class."
	status: "See notice at end of class."
	date: "$Date$"
	revision: "$Revision $"

class PROJECT_EIFFEL_FILE

inherit
	EXCEPTIONS

	UNIX_SIGNALS
		rename
			meaning as sig_meanging,
			ignore as sig_ignore,
			catch as sig_catch
		end

	SYSTEM_CONSTANTS

create
	make

feature {NONE} -- Initialization

	make (a_file_name: like name)
			-- Project file whose location is `a_file_name'.
		require
			a_file_name_not_void: a_file_name /= Void
			a_file_name_not_empty: not a_file_name.is_empty
		do
			error_value := ok_value
			name := a_file_name
			create storage.make_with_path (a_file_name)
		ensure
			name_set: name = a_file_name
		end

feature -- Access

	name: PATH
			-- Name of file for `storage'.

	precompilation_id: INTEGER
			-- Precompilation id when checking for precompilation validity.

	project_version_number: STRING_32
			-- Version number of project eiffel file.

feature -- Store/Retrieval

	retrieved_project: detachable E_PROJECT
			-- Retrieve project.
			-- (Note: error cannot be invalid_precompilation)
		do
			if attached {E_PROJECT} retrieved_object as r then
				Result := r
			end
		ensure
			valid_result: not has_error implies Result /= Void
			version_number_exists: project_version_number /= Void
		end

	retrieved_precompile: detachable PRECOMP_INFO
			-- Retrieve the precompile info of project.
			-- (Note: error cannot be invalid_precompilation)
		do
			if attached {PRECOMP_INFO} retrieved_object as r then
				Result := r
			elseif not has_error then
					-- An error was not detected, that is to say we were able
					-- to retrieve something which is not of type PRECOMP_INFO.
					-- Let's generate an error.
				error_value := corrupt_value
			end
		ensure
			valid_result: not has_error implies Result /= Void
		end

	store (a_project: ANY; a_precompilation_id: INTEGER)
			-- Store `a_project' for version `a_version' and `a_precompilation_id'.
		require
			a_project_not_void: a_project /= Void
		local
			l_writer: SED_MEDIUM_READER_WRITER
			l_serializer: SED_INDEPENDENT_SERIALIZER
			retried: BOOLEAN
		do
			if not retried then
				storage.open_write
				storage.put_string (storage_validity_string)

				create l_writer.make (storage)
				create l_serializer.make (l_writer)
				l_writer.write_header
				l_writer.write_string_32 (version_number)
				l_writer.write_integer_32 (a_precompilation_id)
					-- The following information is not used on retrieval, but may help
					-- users finding out which version of the Eiffel compiler and from where
					-- this Eiffel compiler was coming from.
				l_writer.write_string_32 (eiffel_layout.ec_command_name.name)
				l_writer.write_string_32 (compiler_version_number.version)

				if is_c_storable then
					l_writer.write_footer
						--| To store correctly the information after the project
						--| header, we need to set the position, otherwise the
						--| result is quite strange and won't be retrievable
					storage.flush
					storage.go (storage.count)

					compiler_store (storage.descriptor, $a_project)
				else
					l_serializer.set_root_object (a_project)
					l_serializer.encode
					l_writer.write_footer
				end

				storage.close
			else
				if not storage.is_closed then
					storage.close
				end
				error_value := cannot_store_value
			end
		rescue
			retried := True
			retry
		end

feature -- Status report

	is_interrupted: BOOLEAN
			-- Was the retrieve of the project interrupted?
		do
			Result := error_value = interrupt_value
		end

	is_invalid_precompilation: BOOLEAN
			-- Is the precompilation invalid?
		do
			Result := error_value = invalid_precompilation_value
		end

	is_corrupted: BOOLEAN
			-- Is the project corrupted?
		do
			Result := error_value = corrupt_value
		end

	is_incompatible: BOOLEAN
			-- Is the project incompatible with the current version?
		do
			Result := error_value = incompatible_value
		end;

	is_valid: BOOLEAN
			-- Is current a valid project file?
		do
			Result := storage.is_readable and then storage.is_plain
		end

	error, has_error: BOOLEAN
			-- Did an error occurred during the retrieval?
		do
			Result := error_value /= ok_value
		end;

	error_description: detachable STRING
			-- Error's description if any.
		do
			if has_error then
				Result := "Error [" + error.out + "] "
				inspect error_value
				when corrupt_value then
					Result.append ("corrupt_value")
				when invalid_precompilation_value then
					Result.append ("invalid_precompilation_value")
				when incompatible_value then
					Result.append ("incompatible_value")
				when interrupt_value then
					Result.append ("interrupt_value")
				when cannot_store_value then
					Result.append ("cannot_store_value")
				else
				end
			end
		ensure
			has_error implies Result /= Void
		end

	exists: BOOLEAN
			-- Does `storage' exist?
		do
			Result := storage.exists
		end

	is_readable: BOOLEAN
			-- Is `storage' file readable?
		do
			Result := storage.is_readable
		end

	is_writable: BOOLEAN
			-- Is `storage' file writable?
		do
			Result := storage.is_writable
		end

feature -- Update

	check_version_number (precomp_id: INTEGER)
			-- Check the version number of the project.txt file.
			-- If `precomp_id' is 0 then do not check precompilation_id.
			-- If error ok set the error state.
		local
			l_reader: SED_MEDIUM_READER_WRITER
		do
			error_value := ok_value
			create l_reader.make (storage)
			l_reader.set_for_reading
			if not storage.exists or else not storage.is_readable or else storage.is_directory then
				error_value := corrupt_value;
			else
				storage.open_read
				check_storage
				if not has_error then
					read_project_header (l_reader)
					if not has_error then
						if not project_version_number.same_string (version_number) then
							error_value := incompatible_value;
						elseif precomp_id /= 0 and then precomp_id /= precompilation_id then
							error_value := invalid_precompilation_value;
						end
					end
				else
						-- To satisfy postcondition.
					create project_version_number.make_empty
				end
				storage.close
			end
		ensure
			error_means_incompatible: has_error implies (is_incompatible or is_corrupted or is_invalid_precompilation)
			valid_version_number: not has_error implies project_version_number /= Void
		end;

feature {NONE} -- Implementation

	storage: RAW_FILE
			-- Storage for project file.

	error_value: INTEGER
			-- Error value

	ok_value: INTEGER = 1
	corrupt_value: INTEGER = 2
	invalid_precompilation_value: INTEGER = 3
	incompatible_value: INTEGER = 4
	interrupt_value: INTEGER = 5
	cannot_store_value: INTEGER = 6
			-- Error values

	retrieved_object: ANY
			-- Retrieve project
		local
			retried: BOOLEAN
			l_reader: SED_MEDIUM_READER_WRITER
			l_deserializer: SED_INDEPENDENT_DESERIALIZER
		do
			if not retried then
				check_version_number (0)

				if not has_error then
					storage.open_read
					check_storage

					if not has_error then
						create l_reader.make (storage)
						l_reader.set_for_reading
						create l_deserializer.make (l_reader)
						read_project_header (l_reader)
						if not has_error then
							if is_c_storable then
								l_reader.read_footer
								Result := retrieved_using_old_storable
							else
								l_deserializer.decode (False)
								if not l_deserializer.has_error then
									Result := l_deserializer.last_decoded_object
									l_reader.read_footer
								else
									error_value := corrupt_value
								end
							end
						end
					end
						-- Close the Eiffel Project file.
					storage.close
				end
			else
				if not storage.is_closed then
					storage.close
				end
			end
		ensure
			valid_result: not has_error implies Result /= Void
		rescue
			if is_signal and then signal = Sigint then
				error_value := interrupt_value
			else
				error_value := corrupt_value
			end
			retried := True
			retry
		end

	retrieved_using_old_storable: ANY
			-- Read project file using the C storable.
		require
			is_c_storable: is_c_storable
		local
			l_pos: INTEGER
			retried, l_is_collecting: BOOLEAN
			l_mem: MEMORY
		do
			if not retried then
				l_pos := storage.position
				create l_mem
				l_is_collecting := l_mem.collecting
				l_mem.full_collect
				l_mem.full_coalesce
				l_mem.collection_off
				Result := ise_compiler_retrieved (storage.descriptor, storage.position);
				l_mem.collection_on
				l_mem.full_collect
				l_mem.full_coalesce
				if Result = Void then
					error_value := corrupt_value
				end
			else
				if l_is_collecting then
					l_mem.collection_on
				end
				error_value := corrupt_value
			end
		rescue
			retried := True
			retry
		end

	read_project_header (a_reader: SED_MEDIUM_READER_WRITER)
			-- Read project file to get version info.
		require
			a_reader_not_void: a_reader /= Void
			a_reader_ready_for_reading: a_reader.is_ready_for_reading
			no_error: not has_error
		local
			retried: BOOLEAN
			l_str: STRING_32
		do
			if not retried then
				a_reader.read_header
				project_version_number := a_reader.read_string_32
				precompilation_id := a_reader.read_integer_32
					-- Compiler command name
				l_str := a_reader.read_string_32
					-- Compiler version number
				l_str := a_reader.read_string_32
			else
				error_value := corrupt_value
			end
		rescue
			retried := True
			retry
		end

	check_storage
			-- Check validity of Storage.
		require
			storage_open: storage.is_open_read
		do
			storage.read_stream (storage_validity_string.count)
			if not storage.last_string.is_equal (storage_validity_string) then
				error_value := corrupt_value
			end
		end

	storage_validity_string: STRING = "EiffelStudio_Project"
			-- String to validate that an epr is somewhat valid.

feature {NONE} -- C externals

	is_c_storable: BOOLEAN = True
			-- We still default to C storage format because it is faster
			-- to store.

	ise_compiler_retrieved (f_desc, pos: INTEGER) : ANY
		external
			"C | %"pretrieve.h%""
		alias
			"parsing_retrieve"
		end

	compiler_store (f_desc: INTEGER; obj: POINTER)
		external
			"C | %"pstore.h%""
		alias
			"parsing_store"
		end

invariant
	name_not_void: name /= Void
	storage_not_void: storage /= Void

note
	copyright:	"Copyright (c) 1984-2016, Eiffel Software"
	license:	"GPL version 2 (see http://www.eiffel.com/licensing/gpl.txt)"
	licensing_options:	"http://www.eiffel.com/licensing"
	copying: "[
			This file is part of Eiffel Software's Eiffel Development Environment.
			
			Eiffel Software's Eiffel Development Environment is free
			software; you can redistribute it and/or modify it under
			the terms of the GNU General Public License as published
			by the Free Software Foundation, version 2 of the License
			(available at the URL listed under "license" above).
			
			Eiffel Software's Eiffel Development Environment is
			distributed in the hope that it will be useful, but
			WITHOUT ANY WARRANTY; without even the implied warranty
			of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
			See the GNU General Public License for more details.
			
			You should have received a copy of the GNU General Public
			License along with Eiffel Software's Eiffel Development
			Environment; if not, write to the Free Software Foundation,
			Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
		]"
	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