Class: Mxrb::Semantic::Extractor

Inherits:
Object
  • Object
show all
Defined in:
lib/mxrb/semantic/extractor.rb

Overview

Extracts a contiguous subgraph of a microflow's activities into a new microflow and replaces the selection with a call to it.

Constant Summary collapse

FLOW_KINDS =
%i[microflow nanoflow].freeze

Instance Method Summary collapse

Constructor Details

#initialize(project) ⇒ Extractor

Returns a new instance of Extractor.



194
195
196
# File 'lib/mxrb/semantic/extractor.rb', line 194

def initialize(project)
  @project = project
end

Instance Method Details

#plan(source_name, as:, object_ids:) ⇒ Object

Plan an extraction.

Parameters:

  • source_name (String)

    qualified name of the source microflow

  • as (String)

    qualified name for the new extracted microflow

  • object_ids (Array)

    $ID values of the objects to extract

Raises:

  • (ArgumentError)


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/mxrb/semantic/extractor.rb', line 203

def plan(source_name, as:, object_ids:)
  source = resolve(source_name)
  raise ArgumentError, "#{source.qualified_name} is not a microflow or nanoflow" \
    unless FLOW_KINDS.include?(source.kind)

  source_module = source.qualified_name.split(".").first
  new_module    = as.split(".").first
  raise ArgumentError, "extracted microflow must be in the same module (#{source_module})" \
    unless new_module == source_module

  raise ArgumentError, "#{as.inspect} already exists" if @project.find_artifact(as)
  raise ArgumentError, "object_ids cannot be empty" if object_ids.empty?

  raw = @project.raw_unit(source.unit_id)
  doc = @project.parse_bson(raw)

  all_objects = parse_array(doc["ObjectCollection"]["Objects"]).map { normalize_ids(_1) }
  all_flows   = parse_array(doc["Flows"]).map { normalize_ids(_1) }
  by_id = all_objects.to_h { [_1["$ID"], _1] }

  selected = Set.new(object_ids.map { IO::BsonCodec.extract_id(_1)&.to_s || _1.to_s })
  missing  = selected - by_id.keys.to_set
  raise ArgumentError, "unknown object IDs: #{missing.to_a.join(', ')}" unless missing.empty?

  # Reject selection of start/end events — those belong to the frame
  all_objects.each do |obj|
    if selected.include?(obj["$ID"]) && %w[
      Microflows$StartEvent Microflows$EndEvent
    ].include?(obj["$Type"])
      raise ArgumentError, "cannot include start/end event in extraction selection"
    end
  end

  inner_flows  = all_flows.select do
    selected.include?(_1["OriginPointer"]) && selected.include?(_1["DestinationPointer"])
  end
  entry_flows  = all_flows.select do
    !selected.include?(_1["OriginPointer"]) && selected.include?(_1["DestinationPointer"])
  end
  exit_flows   = all_flows.select do
    selected.include?(_1["OriginPointer"]) && !selected.include?(_1["DestinationPointer"])
  end

  if entry_flows.size != 1 || exit_flows.size != 1
    raise ArgumentError,
          "selection must have exactly one entry point (found #{entry_flows.size}) " \
          "and one exit point (found #{exit_flows.size})"
  end

  inner_objects = all_objects.select { selected.include?(_1["$ID"]) }

  ExtractionPlan.new(
    project: @project,
    source: source,
    new_name: as,
    selected_ids: selected,
    inner_objects: inner_objects,
    inner_flows: inner_flows,
    entry_flow: entry_flows.first,
    exit_flow: exit_flows.first,
    source_doc: doc,
    source_raw: raw
  )
end