indexing description: "Metric manager to maintain all registered metrics in current application targer" legal: "See notice at end of class." status: "See notice at end of class." author: "" date: "$Date$" revision: "$Revision$" class EB_METRIC_MANAGER inherit QL_OBSERVABLE QL_SHARED_UNIT SHARED_WORKBENCH PROJECT_CONTEXT EIFFEL_LAYOUT export {NONE} all end create make feature{NONE} -- Initialization make is -- Initialize. do create {LINKED_LIST [EB_METRIC]} metrics.make create metrics_vadility.make (100) end feature -- Setting clear_last_error is -- Clear `last_error'. do last_error := Void ensure last_error_cleared: last_error = Void and not has_error end feature -- Validation check_validation (a_force: BOOLEAN) is -- Check validation status of metrics in `metrics'. -- If `a_force' is True, check even though some metric has already been checked. local l_cursor: CURSOR do l_cursor := metrics.cursor from metrics.start until metrics.after loop if a_force or not is_metric_vadility_checked (metrics.item.name) then check_validation_for_metric (metrics.item.name) end metrics.forth end metrics.go_to (l_cursor) end check_validation_for_metric (a_name: STRING) is -- Check validation for metric named `a_name'. -- Do not check name crash. require a_name_attached: a_name /= Void metric_exists: has_metric (a_name) local l_validator: EB_METRIC_VADILITY_VISITOR do create l_validator.make (Current) l_validator.process_metric (metric_with_name (a_name)) metrics_vadility.force (l_validator.last_error, a_name) end feature -- Status report has_error: BOOLEAN is -- Does parsing contain error? do Result := last_error /= Void end has_metric (a_name: STRING): BOOLEAN is -- Does `metrics' has metric named `a_name'? require a_name_attached: a_name /= Void local l_metrics: like metrics l_cursor: CURSOR do l_metrics := metrics l_cursor := l_metrics.cursor from l_metrics.start until l_metrics.after or Result loop Result := l_metrics.item.name.is_case_insensitive_equal (a_name) l_metrics.forth end l_metrics.go_to (l_cursor) end is_metric_valid (a_name: STRING): BOOLEAN is -- Is metric named `a_name' valid? require a_name_attached: a_name /= Void metric_exists: has_metric (a_name) do if not is_metric_vadility_checked (a_name) then check_validation_for_metric (a_name) end Result := metric_vadility (a_name) = Void end is_valid_order (a_order: INTEGER): BOOLEAN is -- Is `a_order' is valid sorting order? -- For information of sorting order, see `ascending_order', `descending_order' and `topological_order'. do Result := a_order = ascending_order or a_order = descending_order or a_order = topological_order end is_metric_loaded: BOOLEAN -- Have metrics in files been loaded? is_userdefined_metric_file_exist: BOOLEAN is -- Does user defined metric file exist? local l_file: RAW_FILE do create l_file.make (userdefined_metrics_file) Result := l_file.exists end is_last_predefined_metric_load_successful: BOOLEAN is -- Is last predefined metric loading successful? do Result := last_predefined_metric_load_error = Void end is_last_userdefined_metric_load_successful: BOOLEAN is -- Is last userdefined metric loading successful? do Result := last_userdefined_metric_load_error = Void end feature -- Access userdefined_metrics_file: STRING is -- File to store user-defined metrics require system_defined: workbench.system_defined and then workbench.is_already_compiled local l_file_name: FILE_NAME do create l_file_name.make_from_string (userdefined_metrics_path) l_file_name.set_file_name ("userdefined_metrics.xml") Result := l_file_name.out ensure good_result: Result /= Void and then not Result.is_empty end userdefined_metrics_path: STRING is -- File path where `userdefined_metric_file' is located, not including the trailing directory separator require system_defined: workbench.system_defined and then workbench.is_already_compiled local l_file_name: FILE_NAME do create l_file_name.make_from_string (project_location.path) l_file_name.extend ("metrics") Result := l_file_name.out ensure result_attached: Result /= Void end last_error: EB_METRIC_ERROR -- Last occurred error metrics: LIST [EB_METRIC] -- List of registered metrics metrics_with_unit (a_unit: QL_METRIC_UNIT): LIST [EB_METRIC] is -- List of registered metrics whose unit is `a_unit' local l_metrics: like metrics l_cursor: CURSOR do l_metrics := metrics.twin l_cursor := l_metrics.cursor from l_metrics.start until l_metrics.after loop if l_metrics.item.unit /= a_unit then l_metrics.remove else l_metrics.forth end end l_metrics.go_to (l_cursor) ensure result_attached: Result /= Void end metric_with_name (a_name: STRING): EB_METRIC is -- Metric whose name is `a_name' -- Void if no metric whose name is `a_name' in `metrics'. require a_name_attached: a_name /= Void local l_metrics: like metrics l_cursor: CURSOR do l_metrics := metrics l_cursor := l_metrics.cursor from l_metrics.start until l_metrics.after or Result /= Void loop if l_metrics.item.name.is_case_insensitive_equal (a_name) then Result := l_metrics.item else l_metrics.forth end end l_metrics.go_to (l_cursor) end ordered_metrics (a_order: INTEGER; a_flat: BOOLEAN): HASH_TABLE [LIST [EB_METRIC], QL_METRIC_UNIT] is -- All metrics in `a_order' -- If `a_flat' is True, DO NOT sort metrics according to unit, and all sorted metrics will be in a list with the key `no_unit'. require a_order_valid: is_valid_order (a_order) local l_ordered_metrics: HASH_TABLE [DS_ARRAYED_LIST [EB_METRIC], QL_METRIC_UNIT] l_metric_list: DS_ARRAYED_LIST [EB_METRIC] l_metrics: like metrics l_units: like units l_cursor: CURSOR l_sorter: DS_QUICK_SORTER [EB_METRIC] l_agent_sorter: AGENT_BASED_EQUALITY_TESTER [EB_METRIC] l_list: ARRAYED_LIST [EB_METRIC] do l_units := units l_metrics := metrics if a_flat then create l_metric_list.make (l_metrics.count) end -- Categorize metrics by unit. create l_ordered_metrics.make (11) from l_units.start until l_units.after loop l_ordered_metrics.put (create {DS_ARRAYED_LIST [EB_METRIC]}.make (10), l_units.item) l_units.forth end l_cursor := l_metrics.cursor from l_metrics.start until l_metrics.after loop if a_flat then l_metric_list.force_last (l_metrics.item) else check l_ordered_metrics.item (l_metrics.item.unit) /= Void end l_ordered_metrics.item (l_metrics.item.unit).force_last (l_metrics.item) end l_metrics.forth end l_metrics.go_to (l_cursor) -- Sort metrics in the same unit and store result. create {HASH_TABLE [ARRAYED_LIST [EB_METRIC], QL_METRIC_UNIT]} Result.make (l_ordered_metrics.count) create l_agent_sorter.make (agent metric_order_tester) create l_sorter.make (l_agent_sorter) current_sort_order := a_order if a_flat then l_sorter.sort (l_metric_list) create l_list.make_from_array (l_metric_list.to_array) Result.put (l_list, no_unit) else from l_ordered_metrics.start until l_ordered_metrics.after loop l_sorter.sort (l_ordered_metrics.item_for_iteration) create l_list.make_from_array (l_ordered_metrics.item_for_iteration.to_array) Result.put (l_list, l_ordered_metrics.key_for_iteration) l_ordered_metrics.forth end end ensure result_attached: Result /= Void end metric_vadility (a_name: STRING): EB_METRIC_ERROR is -- Vadility status of metric named `a_name' require a_name_attached: a_name /= Void metric_exists: has_metric (a_name) do if not is_metric_vadility_checked (a_name) then check_validation_for_metric (a_name) end Result := metrics_vadility.item (a_name) end units: LIST [QL_METRIC_UNIT] is -- List of all supported units once create {ARRAYED_LIST [QL_METRIC_UNIT]} Result.make (11) Result.extend (target_unit) Result.extend (group_unit) Result.extend (class_unit) Result.extend (generic_unit) Result.extend (feature_unit) Result.extend (argument_unit) Result.extend (local_unit) Result.extend (assertion_unit) Result.extend (line_unit) Result.extend (compilation_unit) Result.extend (ratio_unit) ensure result_attached: Result /= Void end next_metric_name_with_unit (a_unit: QL_METRIC_UNIT): STRING is -- Next numbered metric name starting with "Unnamed" and with unit `a_unit' require a_unit_attached: a_unit /= Void do Result := next_metric_name ("Unnamed " + a_unit.name + " metric") ensure result_attached: Result /= Void not_result_is_empty: not Result.is_empty end last_loaded_metric_archive: LIST [EB_METRIC_ARCHIVE_NODE] -- Last loaded metric archive by `load_metric_archive' uuid_generator: UUID_GENERATOR is -- UUID generator once create Result ensure result_attached: Result /= Void end last_predefined_metric_load_error: EB_METRIC_ERROR -- Error information when load predefined metric definition the last time -- Void if no error occurred. last_userdefined_metric_load_error: EB_METRIC_ERROR -- Error information when load userdefined metric definition the last time -- Void if no error occurred. feature -- Access/Sorting order ascending_order: INTEGER is 1 -- Ascending order descending_order: INTEGER is 2 -- Descending order topological_order: INTEGER is 3 -- Topological order, i.e., valid metrics come first in ascending order , and then invalid metrics in ascending order. feature -- Metric management load_metrics is -- Load predefined and user-defined metrics. local l_file: RAW_FILE l_error: like last_error l_error_str: STRING do if workbench.system_defined and then workbench.is_already_compiled then clear_last_error block metrics.wipe_out metrics_vadility.wipe_out -- Load predefined metrics. create l_file.make (eiffel_layout.predefined_metrics_file) if not l_file.exists then create {EB_METRIC_ERROR_FILE} l_error.make ("Could not open file", eiffel_layout.predefined_metrics_file) else load_metric_definitions (eiffel_layout.predefined_metrics_file, True) if last_error /= Void then l_error := last_error end end if last_error /= Void then last_predefined_metric_load_error := last_error else last_predefined_metric_load_error := Void end -- Load user-defined metrics. create l_file.make (userdefined_metrics_file) if l_file.exists then if l_file.is_readable then load_metric_definitions (userdefined_metrics_file, False) else create {EB_METRIC_ERROR_FILE} l_error.make ("Could not open file", userdefined_metrics_file) end else last_userdefined_metric_load_error := Void end if not has_error and then l_error /= Void then last_error := l_error end if last_error /= Void then last_userdefined_metric_load_error := last_error else last_userdefined_metric_load_error := Void end if not is_last_predefined_metric_load_successful or not is_last_userdefined_metric_load_successful then create l_error_str.make (256) if not is_last_predefined_metric_load_successful then l_error_str.append ("When loading predefined metrics: ") l_error_str.append (last_predefined_metric_load_error.out) l_error_str.append ("%NPredefined metrics not loaded.%N") end if not is_last_userdefined_metric_load_successful then l_error_str.append ("When loading user-defined metrics: ") l_error_str.append (last_userdefined_metric_load_error.out) l_error_str.append ("%NUser-defined metrics not loaded.%N") end create {EB_METRIC_ERROR_PARSE} last_error.make_with_no_title (l_error_str) end is_metric_loaded := True resume notify (Void) end end load_metric_definitions (a_file_name: STRING; a_predefined: BOOLEAN) is -- Load metric definitions in `a_file_name'. -- If some error occurs when parsing `a_file_name', no metric will be registered in `metrics'. -- If `a_predefined' is True, mark loaded metrics predefined. require a_file_name_attached: a_file_name /= Void not_a_file_name_is_empty: not a_file_name.is_empty local l_loaded_metrics: like metrics do block l_loaded_metrics := metrics_from_file (a_file_name) if not has_error then -- Check metric name crash. -- If some metric in `loaded_metrics' has the same name as a metric in `metrics', -- no metric will be registered in `metrics'. All metrics in `l_loaded_metrics' will be ignored. check_name_crash (l_loaded_metrics) -- Registered metrics in `l_loaded_metrics' into `metrics' if not has_error then l_loaded_metrics.do_all (agent register_metric (?, a_predefined)) end end resume notify (last_error) end store_metric_definitions (a_file_name: STRING) is -- Store non-predefined `metrics' in `a_file_name'. -- Note: Always create new file when store. require a_file_name_attached: a_file_name /= Void not_a_file_name_is_empty: not a_file_name.is_empty local l_file: PLAIN_TEXT_FILE l_xml_generator: EB_METRIC_XML_WRITER l_retried: BOOLEAN do if not l_retried then clear_last_error create l_file.make_create_read_write (a_file_name) create l_xml_generator.make l_file.put_string ("%N") from metrics.start until metrics.after loop if not metrics.item.is_predefined then l_xml_generator.set_indent (1) l_xml_generator.clear_text l_xml_generator.process_metric (metrics.item) l_file.put_string (l_xml_generator.text) end metrics.forth end l_file.put_string ("%N") l_file.close end rescue create{EB_METRIC_ERROR_FILE} last_error.make ("Could not write file", a_file_name) l_retried := True retry end store_userdefined_metrics is -- Store user-defined metrics. local l_folder: DIRECTORY l_retried: BOOLEAN do if not l_retried then create l_folder.make (userdefined_metrics_path) if not l_folder.exists then l_folder.create_dir end store_metric_definitions (userdefined_metrics_file) end rescue l_retried := True create{EB_METRIC_ERROR_FILE} last_error.make ("Could not create directory", userdefined_metrics_path) retry end insert_metric (a_metric: EB_METRIC; a_predefined: BOOLEAN) is -- Insert `a_metric' in `metrics'. -- If `a_predefined' is True, mark `a_metric' as predefined. require a_metric_attached: a_metric /= Void a_metric_not_exist: not has_metric (a_metric.name) do block register_metric (a_metric, a_predefined) check_validation (True) resume notify (last_error) end remove_metric (a_name: STRING) is -- Remove metric named `a_name'. require a_name_attached: a_name /= Void metric_exists:has_metric (a_name) metric_not_predefined: not metric_with_name (a_name).is_predefined local l_metrics: like metrics done: BOOLEAN do block l_metrics := metrics from l_metrics.start until l_metrics.after or done loop if l_metrics.item.name.is_case_insensitive_equal (a_name) then l_metrics.item.set_metric_manager (Void) l_metrics.remove done := True else l_metrics.forth end end check_validation (True) resume notify (last_error) ensure metric_removed: not has_metric (a_name) end update_metric (a_name: STRING; a_metric: EB_METRIC) is -- Update metric named `a_name' with `a_metric'. require a_name_attached: a_name /= Void metric_exists:has_metric (a_name) a_metric_attached: a_metric /= Void metric_not_predefined: not metric_with_name (a_name).is_predefined no_name_crash: not a_name.is_case_insensitive_equal (a_metric.name) implies not has_metric (a_metric.name) local l_renamer: EB_METRIC_RENAME_VISITOR do block remove_metric (a_name) -- Metric name changes. if not a_name.is_case_insensitive_equal (a_metric.name) then create l_renamer.make (a_name, a_metric.name) from metrics.start until metrics.after loop if not metrics.item.is_predefined then l_renamer.process_metric (metrics.item) end metrics.forth end end insert_metric (a_metric, False) check_validation (True) resume notify (last_error) end save_metric (a_metric: EB_METRIC; a_new: BOOLEAN; a_old_metric: EB_METRIC) is -- Save `a_metric' into `metrics'. -- `a_new' is True indicates that `a_metric' is a newly created metric, -- and in this case, `a_old_metric' is the old metric. require a_metric_attached: a_metric /= Void a_new_valid: a_new implies a_old_metric = Void and not a_new implies a_old_metric /= Void no_name_crash: a_new implies not has_metric (a_metric.name) do block if a_new then metrics.extend (a_metric) a_metric.set_metric_manager (Current) else update_metric (a_old_metric.name, a_metric) end resume notify (last_error) end feature -- Metric archive load_metric_archive (a_file_name: STRING) is -- Load metric archive from file named `a_file_name'. -- Store result in `last_loaded_metric_archive'. -- Set `last_loaded_metric_archive' to Void if error occurs. require a_file_name_attached: a_file_name /= Void not_a_file_name_is_empty: not a_file_name.is_empty local l_callback: EB_METRIC_LOAD_ARCHIVE_CALLBACKS do clear_last_error create l_callback.make_with_factory (create{EB_LOAD_METRIC_DEFINITION_FACTORY}) parse_file (a_file_name, l_callback) if not has_error then last_loaded_metric_archive := l_callback.archive.twin else last_loaded_metric_archive := Void end end store_metric_archive (a_file_name: STRING; a_archive: LIST [EB_METRIC_ARCHIVE_NODE]) is -- Store metric archive `a_archive' into file named `a_file_name'. require a_file_name_attached: a_file_name /= Void not_a_file_name_is_empty: not a_file_name.is_empty a_archive_attached: a_archive /= Void local l_file: PLAIN_TEXT_FILE l_retried: BOOLEAN l_xml_generator: EB_METRIC_XML_WRITER do if not l_retried then clear_last_error create l_file.make_create_read_write (a_file_name) create l_xml_generator.make l_file.put_string ("%N") l_xml_generator.set_indent (1) l_xml_generator.clear_text l_xml_generator.process_list (a_archive) l_file.put_string (l_xml_generator.text) l_file.put_string ("%N") l_file.close end rescue l_retried := True create{EB_METRIC_ERROR_FILE} last_error.make ("Could not write file", a_file_name) end feature{NONE} -- Implementation parse_file (a_file: STRING; a_callback: EB_LOAD_METRIC_CALLBACKS) is -- Parse `a_file' using `a_callbacks'. require a_file_ok: a_file /= Void and then not a_file.is_empty a_callback_not_void: a_callback /= Void local l_file: KL_TEXT_INPUT_FILE l_test_file: PLAIN_TEXT_FILE l_parser: XM_PARSER l_ns_cb: XM_NAMESPACE_RESOLVER do create {XM_EIFFEL_PARSER} l_parser.make create l_ns_cb.set_next (a_callback) l_parser.set_callbacks (l_ns_cb) create l_file.make (a_file) create l_test_file.make (a_file) l_file.open_read if l_file.exists and then l_file.is_open_read then l_parser.parse_from_stream (l_file) l_file.close if a_callback.has_error then last_error := a_callback.last_error end else create {EB_METRIC_ERROR_FILE} last_error.make ("Could not open file", a_file) end end register_metric (a_metric: EB_METRIC; a_predefined: BOOLEAN) is -- Register `a_metric' in `metrics'. -- If `a_predefined' is True, mark `a_metric' as predefined metric (User can not edit or delete a predefined metric) require a_metric_attached: a_metric /= Void a_metric_not_exists: not has_metric (a_metric.name) do metrics.extend (a_metric) a_metric.set_metric_manager (Current) a_metric.set_is_predefined (a_predefined) ensure metric_registered: has_metric (a_metric.name) and a_metric.manager = Current and (a_predefined implies a_metric.is_predefined) end is_metric_vadility_checked (a_name: STRING): BOOLEAN is -- Is vadility of metric named `a_name' checked? require a_name_attached: a_name /= Void metric_exists: has_metric (a_name) do Result := metrics_vadility.has (a_name) end metrics_from_file (a_file_name: STRING): LIST [EB_METRIC] is -- Metrics defined in file named `a_file_name' -- If error occurs when opening the file or loading metric definitions, -- result will be Void and the actual error will be in `last_error'. require a_file_name_attached: a_file_name /= Void not_a_file_name_is_empty: not a_file_name.is_empty local l_callback: EB_METRIC_LOAD_DEFINITION_CALLBACKS do clear_last_error create l_callback.make_with_factory (create{EB_LOAD_METRIC_DEFINITION_FACTORY}) parse_file (a_file_name, l_callback) if not has_error then Result := l_callback.metrics end end feature{NONE} -- Implementation metrics_vadility: HASH_TABLE [EB_METRIC_ERROR, STRING] -- Table of metric vadility. -- Key is name of metric, value is the error message. -- Value is Void indicates that the metric is valid. metric_order_tester (a_metric, b_metric: EB_METRIC): BOOLEAN is -- Tester to decide the order of `a_metric' and `b_metric'. require a_metric_attached: a_metric /= Void b_metric_attached: b_metric /= Void local l_metric_a_valid: BOOLEAN l_metric_b_valid: BOOLEAN do if current_sort_order = topological_order then l_metric_a_valid := is_metric_valid (a_metric.name) l_metric_b_valid := is_metric_valid (b_metric.name) if l_metric_a_valid and then not l_metric_b_valid then Result := True elseif not l_metric_a_valid and then l_metric_b_valid then else Result := a_metric.name.as_lower < b_metric.name.as_lower end else if current_sort_order = ascending_order then Result := a_metric.name.as_lower < b_metric.name.as_lower else Result := a_metric.name.as_lower > b_metric.name.as_lower end end end current_sort_order: INTEGER -- Current sort order for metrics check_name_crash (a_metric_list: like metrics) is -- Check if there is a metric in `a_metric_list' which will cause name crash with metrics in `metrics'. require a_metric_list_attached: a_metric_list /= Void local l_metric: EB_METRIC do from a_metric_list.start until a_metric_list.after or has_error loop l_metric := a_metric_list.item check l_metric /= Void end if has_metric (l_metric.name) then create{EB_METRIC_ERROR_EXIST} last_error.make (l_metric.name) end a_metric_list.forth end end next_metric_name (a_name_starter: STRING): STRING is -- Next numbered metric name with starter `a_name_starter' -- For example, if metric named "Unnamed metric#1" exists in `metrics', -- `next_metric_name' with starter "Unnamed metric" will return "Unnamed metric#2" require a_name_starter_attached: a_name_starter /= Void not_a_name_starter_is_empty: not a_name_starter.is_empty local l_metrics: like metrics l_starter_cnt: INTEGER l_item: EB_METRIC l_name: STRING l_largest_index: INTEGER l_index_str: STRING l_index: INTEGER do l_metrics := metrics l_starter_cnt := a_name_starter.count + 1 from l_largest_index := 1 l_metrics.start until l_metrics.after loop l_item := l_metrics.item l_name := l_item.name if l_name.count > l_starter_cnt and then l_name.item (l_starter_cnt) = '#' then l_index_str := l_name.substring (l_starter_cnt + 1, l_name.count) if l_index_str.is_integer then l_index := l_index_str.to_integer if l_index >= l_largest_index then l_largest_index := l_index + 1 end end end l_metrics.forth end create Result.make (l_starter_cnt + 5) Result.append (a_name_starter) Result.append_character ('#') Result.append (l_largest_index.out) ensure result_attached: Result /= Void end invariant metrics_attached: metrics /= Void metrics_vadility_attached: metrics_vadility /= Void indexing copyright: "Copyright (c) 1984-2006, 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 356 Storke Road, Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end