Class: Ea::Transformations::Parsers::BaseParser Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/ea/transformations/parsers/base_parser.rb

Overview

This class is abstract.

Subclass and implement the abstract methods

Base parser interface defining the contract for all model format parsers.

This abstract base class implements the Template Method pattern and follows the Liskov Substitution Principle - all concrete parsers must be substitutable for this base class.

Concrete parsers must implement:

  • parse_internal: Core parsing logic
  • supported_extensions: List of supported file extensions
  • format_name: Human-readable format name

Direct Known Subclasses

QeaParser, XmiParser

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(configuration: nil, options: {}) ⇒ BaseParser

Initialize parser with configuration and options

Parameters:

  • configuration (Configuration) (defaults to: nil)

    Transformation configuration

  • options (Hash) (defaults to: {})

    Parsing options



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ea/transformations/parsers/base_parser.rb', line 33

def initialize(configuration: nil, options: {})
  @configuration = configuration
  @options = default_options.merge(options)
  @errors = []
  @warnings = []
  @parse_stats = {
    total_parses: 0,
    successful_parses: 0,
    failed_parses: 0,
    total_duration: 0,
    durations: [],
  }
  @last_duration = nil
  @stats_mutex = Mutex.new
end

Instance Attribute Details

#configurationConfiguration (readonly)

Returns Parser configuration.

Returns:



21
22
23
# File 'lib/ea/transformations/parsers/base_parser.rb', line 21

def configuration
  @configuration
end

#last_durationFloat? (readonly)

Returns Duration of last parse in seconds.

Returns:

  • (Float, nil)

    Duration of last parse in seconds



27
28
29
# File 'lib/ea/transformations/parsers/base_parser.rb', line 27

def last_duration
  @last_duration
end

#optionsHash (readonly)

Returns Parsing options.

Returns:

  • (Hash)

    Parsing options



24
25
26
# File 'lib/ea/transformations/parsers/base_parser.rb', line 24

def options
  @options
end

Instance Method Details

#add_error(message, context = {}) ⇒ void

This method returns an undefined value.

Add an error message

Parameters:

  • message (String)

    Error message

  • context (Hash) (defaults to: {})

    Additional context information



259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ea/transformations/parsers/base_parser.rb', line 259

def add_error(message, context = {})
  error_entry = {
    message: message,
    context: context,
    timestamp: Time.now,
  }
  @errors << error_entry

  # Log error if configuration allows
  log_error(error_entry) if should_log_errors?
end

#add_info(message, context = {}) ⇒ Object



288
289
290
# File 'lib/ea/transformations/parsers/base_parser.rb', line 288

def add_info(message, context = {})
  add_warning(message, context)
end

#add_warning(message, context = {}) ⇒ void

This method returns an undefined value.

Add a warning message

Parameters:

  • message (String)

    Warning message

  • context (Hash) (defaults to: {})

    Additional context information



276
277
278
279
280
281
282
283
284
285
286
# File 'lib/ea/transformations/parsers/base_parser.rb', line 276

def add_warning(message, context = {})
  warning_entry = {
    message: message,
    context: context,
    timestamp: Time.now,
  }
  @warnings << warning_entry

  # Log warning if configuration allows
  log_warning(warning_entry) if should_log_errors?
end

#after_parse(document, _file_path) ⇒ Lutaml::Uml::Document

Hook called after parsing completes

Parameters:

  • document (Lutaml::Uml::Document)

    Parsed document

  • file_path (String)

    Path to the source file

Returns:

  • (Lutaml::Uml::Document)

    Processed document



234
235
236
237
238
# File 'lib/ea/transformations/parsers/base_parser.rb', line 234

def after_parse(document, _file_path)
  # Default implementation returns document unchanged
  # Subclasses can override for post-processing
  document
end

#assign_if_supported(target, setter, value) ⇒ Object



292
293
294
295
296
# File 'lib/ea/transformations/parsers/base_parser.rb', line 292

def assign_if_supported(target, setter, value)
  target.public_send(setter, value)
rescue NoMethodError
  nil
end

#before_parse(file_path) ⇒ void

This method returns an undefined value.

Hook called before parsing starts

Parameters:

  • file_path (String)

    Path to the file being parsed



224
225
226
227
# File 'lib/ea/transformations/parsers/base_parser.rb', line 224

def before_parse(file_path)
  # Default implementation does nothing
  # Subclasses can override for pre-processing
end

#can_parse?(file_path) ⇒ Boolean

Check if this parser can handle the given file

Parameters:

  • file_path (String)

    Path to the file

Returns:

  • (Boolean)

    true if parser can handle the file



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/ea/transformations/parsers/base_parser.rb', line 104

def can_parse?(file_path) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  extension = File.extname(file_path).downcase
  return true if supported_extensions.include?(extension)

  if File.exist?(file_path)
    File.open(file_path, "rb") do |file|
      header = file.read(1024) # Read first 1KB
      return false if header.nil? || header.empty?

      content_patterns.each do |pattern|
        return true if header.match?(pattern)
      end
    end
  end

  false
end

#clear_errors_and_warningsObject



361
362
363
364
# File 'lib/ea/transformations/parsers/base_parser.rb', line 361

def clear_errors_and_warnings
  @errors.clear
  @warnings.clear
end

#content_patternsObject



144
145
146
# File 'lib/ea/transformations/parsers/base_parser.rb', line 144

def content_patterns
  []
end

#default_optionsHash

Get default parsing options

Returns:

  • (Hash)

    Default options hash



243
244
245
246
247
248
249
250
251
252
# File 'lib/ea/transformations/parsers/base_parser.rb', line 243

def default_options
  {
    validate_input: true,
    validate_output: false,
    include_diagrams: true,
    preserve_ids: true,
    resolve_references: true,
    strict_mode: false,
  }
end

#errorsArray<String>

Get all parsing errors

Returns:

  • (Array<String>)

    List of error messages



164
165
166
# File 'lib/ea/transformations/parsers/base_parser.rb', line 164

def errors
  @errors.dup
end

#execute_parse(file_path) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ea/transformations/parsers/base_parser.rb', line 76

def execute_parse(file_path)
  before_parse(file_path)
  document = parse_internal(file_path)
  document = after_parse(document, file_path)
  validate_output!(document) if should_validate_output?

  @stats_mutex.synchronize { @parse_stats[:successful_parses] += 1 }
  [true, document]
rescue StandardError => e
  [false, handle_parsing_error(e, file_path)]
end

#format_file_size(size) ⇒ Object



297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ea/transformations/parsers/base_parser.rb', line 297

def format_file_size(size)
  units = %w[B KB MB GB]
  size = size.to_f
  unit_index = 0

  while size >= 1024 && unit_index < units.length - 1
    size /= 1024
    unit_index += 1
  end

  "#{size.round(1)} #{units[unit_index]}"
end

#format_nameString

This method is abstract.

Implement in subclass

Get parser format name

Returns:

  • (String)

    Human-readable format name

Raises:

  • (NotImplementedError)


126
127
128
# File 'lib/ea/transformations/parsers/base_parser.rb', line 126

def format_name
  raise NotImplementedError, "Subclasses must implement #format_name"
end

#format_statistics(stats) ⇒ Object



310
311
312
313
# File 'lib/ea/transformations/parsers/base_parser.rb', line 310

def format_statistics(stats)
  parts = stats.map { |key, value| "#{value} #{key}" }
  parts.join(", ")
end

#has_errors?Boolean

Check if parser has any errors

Returns:

  • (Boolean)

    true if there are parsing errors



150
151
152
# File 'lib/ea/transformations/parsers/base_parser.rb', line 150

def has_errors?
  !@errors.empty?
end

#has_warnings?Boolean

Check if parser has any warnings

Returns:

  • (Boolean)

    true if there are parsing warnings



157
158
159
# File 'lib/ea/transformations/parsers/base_parser.rb', line 157

def has_warnings?
  !@warnings.empty?
end

#parse(file_path) ⇒ Lutaml::Uml::Document

Parse a model file into a UML document

This is the main public interface method that implements the Template Method pattern. It handles common concerns like validation, error handling, and post-processing.

Parameters:

  • file_path (String)

    Path to the model file

Returns:

  • (Lutaml::Uml::Document)

    Parsed UML document

Raises:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/ea/transformations/parsers/base_parser.rb', line 58

def parse(file_path)
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @stats_mutex.synchronize { @parse_stats[:total_parses] += 1 }
  parse_succeeded = false
  parse_handled = false

  begin
    validate_file!(file_path) if should_validate_input?
    clear_errors_and_warnings

    parse_succeeded, result = execute_parse(file_path)
    parse_handled = !parse_succeeded
    result
  ensure
    record_parse_stats(parse_succeeded, parse_handled, start_time)
  end
end

#parse_internal(file_path) ⇒ Lutaml::Uml::Document

This method is abstract.

Implement in subclass

Core parsing implementation

Parameters:

  • file_path (String)

    Path to the file to parse

Returns:

  • (Lutaml::Uml::Document)

    Parsed UML document

Raises:

  • (NotImplementedError)


216
217
218
# File 'lib/ea/transformations/parsers/base_parser.rb', line 216

def parse_internal(file_path)
  raise NotImplementedError, "Subclasses must implement #parse_internal"
end

#priorityObject

Default parser priority



140
141
142
# File 'lib/ea/transformations/parsers/base_parser.rb', line 140

def priority
  100
end

#record_parse_stats(succeeded, handled, start_time) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ea/transformations/parsers/base_parser.rb', line 88

def record_parse_stats(succeeded, handled, start_time)
  unless succeeded || handled
    @stats_mutex.synchronize { @parse_stats[:failed_parses] += 1 }
  end
  duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
  @last_duration = duration
  @stats_mutex.synchronize do
    @parse_stats[:total_duration] += duration
    @parse_stats[:durations] << duration
  end
end

#reset_statisticsvoid

This method returns an undefined value.

Reset all parsing statistics



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ea/transformations/parsers/base_parser.rb', line 198

def reset_statistics
  @stats_mutex.synchronize do
    @parse_stats = {
      total_parses: 0,
      successful_parses: 0,
      failed_parses: 0,
      total_duration: 0,
      durations: [],
    }
  end
  @last_duration = nil
end

#should_fail_fast?Boolean

Check if parser should fail fast on errors

Returns:

  • (Boolean)

    true if parser should fail on first error



340
341
342
# File 'lib/ea/transformations/parsers/base_parser.rb', line 340

def should_fail_fast?
  @configuration&.error_handling&.fail_fast == true
end

#should_log_errors?Boolean

Check if errors should be logged

Returns:

  • (Boolean)

    true if errors should be logged



333
334
335
# File 'lib/ea/transformations/parsers/base_parser.rb', line 333

def should_log_errors?
  @configuration&.error_handling&.log_errors != false
end

#should_validate_input?Boolean

Check if input validation is enabled

Returns:

  • (Boolean)

    true if input should be validated



318
319
320
# File 'lib/ea/transformations/parsers/base_parser.rb', line 318

def should_validate_input?
  @options[:validate_input]
end

#should_validate_output?Boolean

Check if output validation is enabled

Returns:

  • (Boolean)

    true if output should be validated



325
326
327
328
# File 'lib/ea/transformations/parsers/base_parser.rb', line 325

def should_validate_output?
  @options[:validate_output] ||
    @configuration&.transformation_options&.validate_output
end

#statisticsHash

Get parsing statistics

Returns:

  • (Hash)

    Statistics about the parsing process



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ea/transformations/parsers/base_parser.rb', line 178

def statistics
  stats = @stats_mutex.synchronize { @parse_stats.dup }
  durations = stats[:durations]
  {
    format: format_name,
    errors: @errors.size,
    warnings: @warnings.size,
    options: @options,
    total_parses: stats[:total_parses],
    successful_parses: stats[:successful_parses],
    failed_parses: stats[:failed_parses],
    success_rate: calculate_success_rate(stats),
    average_duration: calculate_average_duration(durations),
    total_duration: stats[:total_duration],
  }
end

#supported_extensionsArray<String>

This method is abstract.

Implement in subclass

Get list of supported file extensions

Returns:

  • (Array<String>)

    List of extensions (e.g., [".xmi", ".xml"])

Raises:

  • (NotImplementedError)


134
135
136
137
# File 'lib/ea/transformations/parsers/base_parser.rb', line 134

def supported_extensions
  raise NotImplementedError,
        "Subclasses must implement #supported_extensions"
end

#validate_file!(file_path) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/ea/transformations/parsers/base_parser.rb', line 347

def validate_file!(file_path)
  if file_path.nil? || file_path.empty?
    raise ArgumentError, "File path cannot be nil"
  end

  unless File.exist?(file_path)
    raise ArgumentError, "File does not exist: \#{file_path}"
  end

  unless File.readable?(file_path)
    raise ArgumentError, "File is not readable: \#{file_path}"
  end
end

#validate_output!(document) ⇒ Object



366
367
368
369
370
371
372
373
374
# File 'lib/ea/transformations/parsers/base_parser.rb', line 366

def validate_output!(document)
  unless document.is_a?(Lutaml::Uml::Document)
    raise ArgumentError, "Parser must return a Lutaml::Uml::Document"
  end

  if document.packages.nil? && document.classes.nil?
    add_warning("Document contains no packages or classes")
  end
end

#warningsArray<String>

Get all parsing warnings

Returns:

  • (Array<String>)

    List of warning messages



171
172
173
# File 'lib/ea/transformations/parsers/base_parser.rb', line 171

def warnings
  @warnings.dup
end