note description: "Loads a configuration from a file." legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" revision: "$Revision$" class CONF_LOAD inherit CONF_ACCESS SHARED_CONF_SETTING create make feature {NONE} -- Initialization make (a_factory: like factory) -- Create. require a_factory_not_void: a_factory /= Void do factory := a_factory is_following_redirection := True -- Initialize configuration lib setup, this is done once. initialize_conf_setting -- Refresh mapping in case it was updated since last configuration processing conf_location_mapper.refresh ensure factory_set: factory = a_factory end feature -- Status is_error: BOOLEAN -- Was there an error during the retrieval? is_warning: BOOLEAN -- Were there warnings during the retrieval? is_invalid_xml: BOOLEAN -- Is the file not even valid xml? is_following_redirection: BOOLEAN -- Is loader following redirection? --| True by default last_error: detachable CONF_ERROR -- The last error message. last_warnings: detachable ARRAYED_LIST [CONF_ERROR] -- The last warning messages. last_warning_messages: detachable STRING_32 -- Warning messages as a single string. do if attached last_warnings as l_last_warnings then create Result.make (20) across l_last_warnings as ic loop Result.append (ic.text) Result.append_character ('%N') end end end feature -- Error changes report_error (e: detachable CONF_ERROR) require e_attached: e /= Void do is_error := True last_error := e end reset_error do is_error := False last_error := Void end feature -- Access last_system: detachable CONF_SYSTEM -- The last retrieved system. --| note: the loading follows the redirection, so it may be the system after redirection. last_redirection: detachable CONF_REDIRECTION -- Last retrieved redirection. last_redirected_location: detachable PATH -- Last redirected location. last_uuid: detachable UUID -- The last retrieved uuid. parent_target: detachable CONF_TARGET -- Associated target, when loading a library ecf file for a target. feature -- Element change set_parent_target (a_target: like parent_target) -- Set `parent_target` to `a_target`. do parent_target := a_target end feature -- Basic operation retrieve_and_check_configuration (a_file: READABLE_STRING_32) -- Retrieve the configuration in `a_file' and make it available in `last_system'. -- Also report as warning any validity issue, and as error cycle in (non remote) parent target. require a_file_ok: a_file /= Void and then not a_file.is_empty local l_parent_checker: CONF_PARENT_TARGET_CHECKER l_group_checker: CONF_GROUPS_TARGET_CHECKER do retrieve_configuration (a_file) if attached last_system as syst then if not is_error then create l_parent_checker.make (factory) l_parent_checker.report_issue_as_warning l_parent_checker.resolve_system (syst) if attached l_parent_checker.last_cycle_error as l_cycle_err then report_error (l_cycle_err) elseif attached l_parent_checker.last_error as err then is_warning := True add_warning (err) end end if not is_error then create l_group_checker.make l_group_checker.report_issue_as_warning l_group_checker.check_system (syst) end end ensure no_error_implies_last_system_not_void: not is_error implies last_system /= Void end retrieve_configuration (a_file: READABLE_STRING_32) -- Retrieve the configuration in `a_file' and make it available in `last_system'. require a_file_ok: a_file /= Void and then not a_file.is_empty do last_system := Void last_redirection := Void last_redirected_location := Void last_warnings := Void reset_error recursive_retrieve_configuration (a_file, Void, Void) ensure no_error_implies_last_system_not_void: not is_error implies last_system /= Void end retrieve_uuid (a_file: READABLE_STRING_32) -- Retrieve the uuid of the configuration in `a_file' and make it available in `last_uuid'. require a_file_ok: a_file /= Void and then not a_file.is_empty do last_redirection := Void last_redirected_location := Void last_uuid := Void last_warnings := Void reset_error recursive_retrieve_uuid (a_file, Void, Void) ensure no_error_implies_last_uuid_not_void: not is_error implies last_uuid /= Void end set_is_following_redirection (b: BOOLEAN) -- Set `is_following_redirection' to `b'. do is_following_redirection := b end add_warning (a_warning: CONF_ERROR) -- Add `a_warning'. require a_warning_not_void: a_warning /= Void local l_last_warnings: like last_warnings do l_last_warnings := last_warnings if l_last_warnings = Void then create l_last_warnings.make (1) last_warnings := l_last_warnings end l_last_warnings.extend (a_warning) end feature {CONF_LOAD} -- Implementation recursive_retrieve_configuration (a_file: READABLE_STRING_32; ctx: detachable CONF_LOAD_CONTEXT; a_previous_redirection: detachable TUPLE [file: READABLE_STRING_32; uuid: UUID]) -- Retrieve the configuration in `a_file' and make it available in `last_system', -- it might occur inside an ecf redirection process. -- `ctx' is used to keep track of potential cycle in redirection. -- `a_previous_redirection' is used to carry the file+uuid of previous redirected file -- the `file' is used to report the error with the details of the various implied ecf files. require a_file_ok: a_file /= Void and then not a_file.is_empty local l_callback: CONF_LOAD_PARSE_CALLBACKS l_context: detachable CONF_LOAD_CONTEXT l_previous: detachable TUPLE [file: READABLE_STRING_32; uuid: UUID] do create l_callback.make_with_factory (a_file, factory) parse_file (a_file, l_callback) if l_callback.is_error then is_invalid_xml := l_callback.is_invalid_xml report_error (l_callback.last_error) elseif not is_error then last_system := l_callback.last_system l_context := ctx if l_context = Void then create l_context.make end if attached l_callback.last_redirection as l_redirection then if last_redirection = Void then last_redirection := l_redirection last_redirected_location := l_redirection.file_path end -- This means that `a_file' is a redirection, let's follow the new location -- if `l_redirection.redirection_location' is an absolute path, it will be used as is -- otherwise compute the path relative to the parent folder or a_file if attached l_redirection.uuid as l_new_location_uuid then --| `a_file' has a UUID, then let's check if it is already in a redirection chain, and if ever --| a UUID was previously set. If this is the case, if UUIDs are not the same --| report UUID mismatch error if a_previous_redirection /= Void and then a_previous_redirection.uuid /~ l_new_location_uuid then --| The previously recorded UUID and the UUID from the `a_file' does not match -> report error report_error (create {CONF_ERROR_UUID_MISMATCH_IN_REDIRECTION}.make (a_previous_redirection.file, a_file, a_previous_redirection.uuid, l_new_location_uuid)) else if a_previous_redirection /= Void then --| Update previous redirection with new uuid, so that caller gets new data --| when the recursive redirection instructions are done --| note that `a_previous_redirection.uuid' should already be the same as `l_new_location_uuid' --| but let's assign it again, just in case. check a_previous_redirection.uuid ~ l_new_location_uuid end a_previous_redirection.file := a_file l_previous := a_previous_redirection else --| There was no previous data for redirection --| this means, either this is the first redirection in the chain --| or the previous redirection did not set the UUID value (which is optional) l_previous := [a_file, l_new_location_uuid] end end elseif a_previous_redirection /= Void then --| `a_file' does not have any UUID value --| and previous redirection had a UUID value --| follow the redirection and keep UUID from `a_previous_redirection' --| example: --| [redirection a.ecf with UUID /= Void ] -> [redirection b.ecf with UUID = Void] --| then keep previous file+uuid record. l_previous := a_previous_redirection else --| `a_file' does not have any UUID, and no previous redirection has any UUID --| example: --| [redirection a.ecf with UUID = Void ] -> [redirection b.ecf with UUID = Void] --| then do not record the file and uuid. l_previous := Void end if not is_error and is_following_redirection then --| `a_file' is a redirection, then follow the redirection and check the ecf at the new location --| this will either end with a concrete ecf file, i.e not a redirection, or with an error. if attached l_redirection.message as msg then retrieve_redirected_configuration (a_file, l_redirection.evaluated_redirection_location, l_previous, l_context) is_warning := True add_warning (create {CONF_ERROR_MESSAGE_IN_REDIRECTION}.make (a_file, l_redirection.redirection_location, msg)) else retrieve_redirected_configuration (a_file, l_redirection.evaluated_redirection_location, l_previous, l_context) end end elseif attached last_system as l_last_system then --| found --| i.e: no redirection l_last_system.set_file_name (a_file) l_last_system.set_file_date --| if `a_file' is the end of a redirection chain --| if there is a UUID mismatch, report error if a_previous_redirection /= Void and then a_previous_redirection.uuid /~ l_last_system.uuid then if l_last_system.is_generated_uuid then --| i.e `a_file' does not set any uuid, so it was internally set to new generated UUID. report_error (create {CONF_ERROR_UUID_MISMATCH_IN_REDIRECTION}.make (a_previous_redirection.file, a_file, a_previous_redirection.uuid, Void)) else report_error (create {CONF_ERROR_UUID_MISMATCH_IN_REDIRECTION}.make (a_previous_redirection.file, a_file, a_previous_redirection.uuid, l_last_system.uuid)) end end else --| `last_system' is Void AND `last_location' is also Void. --| --| This means that `a_file' does not have --| --| and neither --| --| This is not valid, then report the error l_callback.set_internal_error is_invalid_xml := l_callback.is_invalid_xml if attached l_callback.last_error as err then report_error (err) --| It could be "Tag %"system%" or %"redirection%" not found", --| but redirection should be used occasionally, so let's not confuse the user. err.set_message ("Tag %"system%" not found") else reset_error end end end ensure no_error_implies_last_system_not_void: not is_error implies last_system /= Void end recursive_retrieve_uuid (a_file: READABLE_STRING_32; a_redirections: detachable ARRAYED_LIST [PATH]; a_previous_redirection: detachable TUPLE [file: READABLE_STRING_32; uuid: UUID]) -- Retrieve the uuid of the configuration in `a_file' and make it available in `last_uuid', -- it might occur during an ecf redirection process. -- `a_redirections' is used to keep track of potential cycle in redirection. -- `a_previous_redirection' is used to carry the file+uuid of previous redirected file require a_file_ok: a_file /= Void and then not a_file.is_empty local l_callback: CONF_LOAD_UUID_CALLBACKS l_err: CONF_ERROR_UUID redir: detachable ARRAYED_LIST [PATH] l_previous: detachable TUPLE [file: READABLE_STRING_32; uuid: UUID] l_redirected_location: like last_redirected_location l_file_parent: PATH do l_file_parent := (create {PATH}.make_from_string (a_file)).parent -- Directory location containing `a_file`. create l_callback.make_with_file (a_file) parse_file (a_file, l_callback) if attached l_callback.last_redirected_location as l_new_location then if attached parent_target as tgt then l_redirected_location := conf_redirection_location_for_file ( factory.new_location_from_full_path ( l_new_location.as_string_32, tgt ).evaluated_path_relative_to_location (l_file_parent).name, a_file ) else l_redirected_location := conf_redirection_location_for_file (l_new_location, a_file) end last_redirected_location := l_redirected_location end if l_callback.is_error then report_error (l_callback.last_error) elseif not is_error then if attached l_callback.last_uuid as l_new_location_uuid then --| `a_file' has a UUID, then let's check if it is already in a redirection chain, and if ever --| a UUID was previously set. If this is the case, if UUIDs are not the same --| report UUID mismatch error last_uuid := l_new_location_uuid if a_previous_redirection /= Void and then a_previous_redirection.uuid /~ l_new_location_uuid then --| The previously recorded UUID and the UUID from the `a_file' does not match -> report error report_error (create {CONF_ERROR_UUID_MISMATCH_IN_REDIRECTION}.make (a_previous_redirection.file, a_file, a_previous_redirection.uuid, l_new_location_uuid)) else if a_previous_redirection /= Void then --| Update previous redirection with new uuid, so that caller gets new data --| when the recursive redirection instructions are done --| note that `a_previous_redirection.uuid' should already be the same as `l_new_location_uuid' --| but let's assign it again, just in case. check a_previous_redirection.uuid ~ l_new_location_uuid end a_previous_redirection.file := a_file l_previous := a_previous_redirection else --| There was no previous data for redirection --| this means, either this is the first redirection in the chain --| or the previous redirection did not set the UUID value (which is optional) l_previous := [a_file, l_new_location_uuid] end end else --| The uuid attribute is not set, which is accepted last_uuid := Void if a_previous_redirection /= Void then --| `a_file' does not have any UUID value --| and previous redirection had a UUID value --| follow the redirection and keep UUID from `a_previous_redirection' --| example: --| [redirection a.ecf with UUID /= Void ] -> [redirection b.ecf with UUID = Void] --| then keep previous file+uuid record. l_previous := a_previous_redirection else --| `a_file' does not have any UUID, and no previous redirection has any UUID --| example: --| [redirection a.ecf with UUID = Void ] -> [redirection b.ecf with UUID = Void] --| then do not record the file and uuid. l_previous := Void end end if not is_error then if attached l_callback.last_redirected_location as l_new_location then -- Warning: do not reuse `last_redirected_location`, -- as it is the first redirection, here we really care about `l_callback.last_redirected_location`. if attached parent_target as tgt then l_redirected_location := conf_redirection_location_for_file ( factory.new_location_from_full_path ( l_new_location.as_string_32, tgt ).evaluated_path_relative_to_location (l_file_parent).name, a_file ) else l_redirected_location := conf_redirection_location_for_file (l_new_location, a_file) end --| `a_file' is a redirection --| then check the ecf at the `l_new_location' redir := a_redirections if redir = Void then create redir.make (1) end if is_following_redirection then retrieve_redirected_uuid (a_file, l_redirected_location.name, redir, l_previous) end elseif l_previous /= Void then last_uuid := l_previous.uuid else --| No UUID found create l_err l_err.set_position (a_file, 1, 1) report_error (l_err) end end end ensure no_error_implies_last_uuid_not_void: not is_error implies last_uuid /= Void end feature {NONE} -- Redirection retrieve_redirected_configuration (a_file: READABLE_STRING_32; a_new_location: READABLE_STRING_GENERAL; a_previous_redirection: detachable TUPLE [file: READABLE_STRING_32; uuid: UUID]; ctx: CONF_LOAD_CONTEXT ) -- Retrieve the configuration in `a_file' and make it available in `last_system', -- knowing that `a_file' describes a redirection to `a_new_location'. -- `a_redirections' is used to keep track of potential cycle in redirection. -- `a_previous_redirection' is used to carry the file+uuid of previous redirected file mainly to check UUID mismatch --| i.e: if we have old_path/abc.ecf which is a redirection to new_path/abc.ecf, the UUID of both abc.ecf should be the same --| however the UUID in the redirection is optional, note that the redirection can be a chain, for instance: --| a.ecf -> b.ecf -> c.ecf --| if a.ecf has a UUID, and b.ecf has no UUID and c.ecf has UUID --| the code checks that there is no UUID mismatch, i.e that UUID of a.ecf is the same as UUID of c.ecf require a_file_ok: a_file /= Void and then not a_file.is_empty a_new_location_ok: a_new_location /= Void and then not a_new_location.is_empty ctx_attached: ctx /= Void local p: PATH do --| If a_new_location is an absolute path, use it --| otherwise compute the absolute path with a_new_location as relative to parent folder of `a_file' -- On linux, replace \ by / -- (see `{CONF_LOCATION}.update_path_to_unix') p := conf_redirection_location_for_file (a_new_location, a_file) if across ctx.redirections as c some p.is_same_file_as (c) end then --| `a_new_location' already appears in the redirection chain --| this is a cycle in redirection which not allowed. report_error (create {CONF_ERROR_CYCLE_IN_REDIRECTION}.make (p.name, ctx.redirections)) else --| Follow the redirection, and record the current redirection node in `a_redirections' ctx.record_redirection (p) recursive_retrieve_configuration (p.name, ctx, a_previous_redirection) end end retrieve_redirected_uuid (a_file: READABLE_STRING_32; a_new_location: READABLE_STRING_GENERAL; a_redirections: ARRAYED_LIST [PATH]; a_previous_redirection: detachable TUPLE [file: READABLE_STRING_32; uuid: UUID]) -- Retrieve the uuid of the configuration in `a_file' and make it available in `last_uuid', -- knowing that `a_file' describes a redirection to `a_new_location'. -- `a_redirections' is used to keep track of potential cycle in redirection. require a_file_ok: a_file /= Void and then not a_file.is_empty a_new_location_ok: a_new_location /= Void and then not a_new_location.is_empty a_redirections_attached: a_redirections /= Void local p: PATH do -- If a_new_location is an absolute path, use it -- otherwise compute the absolute path with a_new_location as relative to parent folder of `a_file' p := (create {PATH}.make_from_string (a_new_location)).absolute_path_in ((create {PATH}.make_from_string (a_file)).parent) if across a_redirections as c some p.is_same_file_as (c) end then --| `a_new_location' already appears in the redirection chain --| this is a cycle in redirection which not allowed. report_error (create {CONF_ERROR_CYCLE_IN_REDIRECTION}.make (p.name, a_redirections)) else --| Follow the redirection until a UUID is set. a_redirections.extend (p) recursive_retrieve_uuid (p.name, a_redirections, a_previous_redirection) end end feature {NONE} -- Implementation conf_location_value_to_path (a_path: READABLE_STRING_GENERAL): PATH -- Return path created updating `a_path' to Unix by changing all windows separator to Unix one. require a_path_not_void: a_path /= Void local s: STRING_32 do if {PLATFORM}.is_windows then create Result.make_from_string (a_path) else create s.make_from_string_general (a_path) s.replace_substring_all ({STRING_32} "\", {STRING_32} "/") create Result.make_from_string (s) end end conf_redirection_location_for_file (a_new_location: READABLE_STRING_GENERAL; a_file_location: READABLE_STRING_GENERAL): PATH -- Resolved redirection location `a_new_location` related to file `a_file_location`. do Result := conf_location_value_to_path (a_new_location) Result := Result.absolute_path_in ((create {PATH}.make_from_string (a_file_location)).parent) end factory: CONF_PARSE_FACTORY -- Factory to create nodes. parse_file (a_file: READABLE_STRING_32; a_callback: CONF_LOAD_CALLBACKS) -- Parse `a_file' using `a_callbacks'. require a_file_ok: a_file /= Void a_callback_not_void: a_callback /= Void local l_file: PLAIN_TEXT_FILE l_parser: detachable XML_PARSER l_ns_cb: XML_NAMESPACE_RESOLVER l_end_tag_checker: XML_END_TAG_CHECKER l_pos: detachable XML_POSITION l_retried: BOOLEAN do if not l_retried then reset_error if a_file.is_empty then report_error (create {CONF_ERROR_FILE}.make (a_file)) else create {XML_STOPPABLE_PARSER} l_parser.make a_callback.set_associated_parser (l_parser) create l_end_tag_checker.set_next (a_callback) l_end_tag_checker.set_associated_parser (l_parser) create l_ns_cb.set_next (l_end_tag_checker) l_ns_cb.set_associated_parser (l_parser) l_parser.set_callbacks (l_ns_cb) create l_file.make_with_name (a_file) if l_file.exists and then l_file.is_plain then l_file.open_read end if l_file.is_open_read then l_parser.parse_from_file (l_file) l_file.close else report_error (create {CONF_ERROR_FILE}.make (a_file)) end end else if l_parser /= Void then --| not is_error implies l_parser /= Void -- In case it is an internal error (Call on Void target, or others...) -- we need to properly handle this. if attached a_callback.last_error as l_cb_error then if attached l_parser.error_position as l_err_pos then l_pos := l_err_pos else l_pos := l_parser.position end l_cb_error.set_position (l_pos.source_name, l_pos.row, l_pos.column) l_cb_error.set_xml_parse_mode else -- Since no error was retrieved it means that we had an internal -- failure. Create an internal error instead. a_callback.set_internal_error end else a_callback.set_internal_error end if attached a_callback.last_error as err then report_error (err) else check is_error_expected: is_error end end end -- add warnings is_warning := is_warning or a_callback.is_warning if attached last_warnings as l_curr_last_warnings then if attached a_callback.last_warning as l_last_warnings and then not l_last_warnings.is_empty then l_curr_last_warnings.append (l_last_warnings) end else last_warnings := a_callback.last_warning end rescue l_retried := True retry end invariant factory_not_void: factory /= Void note copyright: "Copyright (c) 1984-2021, 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