Class: Roby::Queries::PlanObjectMatcher

Inherits:
MatcherBase show all
Includes:
DRoby::V5::Queries::PlanObjectMatcherDumper
Defined in:
lib/roby/queries/plan_object_matcher.rb,
lib/roby/droby/enable.rb

Overview

Predicate that matches characteristics on a plan object

Instance Attribute Summary collapse

Attributes inherited from MatcherBase

#neg_predicates, #predicates

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DRoby::V5::Queries::PlanObjectMatcherDumper

#droby_dump

Methods inherited from MatcherBase

#&, declare_class_methods, #describe_failed_match, #each, #match, match_predicates, #negate, #|

Constructor Details

#initialize(instance = nil) ⇒ PlanObjectMatcher

Initializes an empty TaskMatcher object



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/roby/queries/plan_object_matcher.rb', line 57

def initialize(instance = nil)
    @instance             = instance
    @indexed_query        = !@instance
    @model                = Array.new
    @predicates           = Array.new
    @neg_predicates       = Array.new
    @indexed_predicates     = Array.new
    @indexed_neg_predicates = Array.new
    @owners               = Array.new
    @parents              = Hash.new
    @children             = Hash.new
end

Instance Attribute Details

#childrenHash (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Per relation list of out-edges that the matched object is expected to have



54
55
56
# File 'lib/roby/queries/plan_object_matcher.rb', line 54

def children
  @children
end

#indexed_neg_predicatesArray<Symbol> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Set of predicates that should be false on the object, and for which the index maintains a set of objects for which it is true



40
41
42
# File 'lib/roby/queries/plan_object_matcher.rb', line 40

def indexed_neg_predicates
  @indexed_neg_predicates
end

#indexed_predicatesArray<Symbol> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Set of predicates that should be true on the object, and for which the index maintains a set of objects for which it is true



32
33
34
# File 'lib/roby/queries/plan_object_matcher.rb', line 32

def indexed_predicates
  @indexed_predicates
end

#instancenil, Object (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The actual instance that should match



10
11
12
# File 'lib/roby/queries/plan_object_matcher.rb', line 10

def instance
  @instance
end

#modelArray<Class> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

A set of models that should be provided by the object



17
18
19
# File 'lib/roby/queries/plan_object_matcher.rb', line 17

def model
  @model
end

#ownersArray<DRobyID> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Set of owners that the object should have



24
25
26
# File 'lib/roby/queries/plan_object_matcher.rb', line 24

def owners
  @owners
end

#parentsHash (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Per-relation list of in-edges that the matched object is expected to have



47
48
49
# File 'lib/roby/queries/plan_object_matcher.rb', line 47

def parents
  @parents
end

Class Method Details

.match_predicate(name, positive_index = nil, negative_index = nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/roby/queries/plan_object_matcher.rb', line 114

def match_predicate(name, positive_index = nil, negative_index = nil)
    method_name = name.to_s.gsub(/\?$/, '')
    if Index::PREDICATES.include?(name)
        indexed_predicate = true
        positive_index ||= [["#{name}"], []]
        negative_index ||= [[], ["#{name}"]]
    end
    positive_index ||= [[], []]
    negative_index ||= [[], []]
    class_eval "    def \#{method_name}\n        if neg_predicates.include?(:\#{name})\n            raise ArgumentError, \"trying to match (\#{name} & !\#{name})\"\n        end\n        \#{\"@indexed_query = false\" if !indexed_predicate}\n        predicates << :\#{name}\n        \#{if !positive_index[0].empty? then [\"indexed_predicates\", *positive_index[0]].join(\" << :\") end}\n        \#{if !positive_index[1].empty? then [\"indexed_neg_predicates\", *positive_index[1]].join(\" << :\") end}\n        self\n    end\n    def not_\#{method_name}\n        if predicates.include?(:\#{name})\n            raise ArgumentError, \"trying to match (\#{name} & !\#{name})\"\n        end\n        \#{\"@indexed_query = false\" if !indexed_predicate}\n        neg_predicates << :\#{name}\n        \#{if !negative_index[0].empty? then [\"indexed_predicates\", *negative_index[0]].join(\" << :\") end}\n        \#{if !negative_index[1].empty? then [\"indexed_neg_predicates\", *negative_index[1]].join(\" << :\") end}\n        self\n    end\n    EOD\n    declare_class_methods(method_name, \"not_\#{method_name}\")\nend\n", __FILE__, __LINE__+1

Instance Method Details

#===(object) ⇒ Boolean

Tests whether the given object matches this predicate



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/roby/queries/plan_object_matcher.rb', line 267

def ===(object)
    if instance
        return false if object != instance
    end

    if !model.empty?
        return unless object.fullfills?(model)
    end

    for parent_spec in @parents
        result = handle_parent_child_match(object, parent_spec) do |relation, m, relation_options|
            object.each_parent_object(relation).
                any? { |parent| m === parent && (!relation_options || relation_options === parent[object, relation]) }
        end
        return false if !result
    end

    for child_spec in @children
        result = handle_parent_child_match(object, child_spec) do |relation, m, relation_options|
            object.each_child_object(relation).
                any? { |child| m === child && (!relation_options || relation_options === object[child, relation]) }
        end
        return false if !result
    end

    for pred in predicates
        return false if !object.send(pred)
    end
    for pred in neg_predicates
        return false if object.send(pred)
    end

    return false if !owners.empty? && !(object.owners - owners).empty?
    true
end

#executable?Object

:method: not_executable

Matches if the object is not executable

See also #executable, PlanObject#executable?



163
# File 'lib/roby/queries/plan_object_matcher.rb', line 163

match_predicates :executable?

#filter(initial_set, index, initial_is_complete: false) ⇒ Set

Filters the tasks in initial_set by using the information in index, and returns the result. The resulting set must include all tasks in initial_set which match with #===, but can include tasks which do not match #===



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/roby/queries/plan_object_matcher.rb', line 341

def filter(initial_set, index, initial_is_complete: false)
    positive_sets, negative_sets = indexed_sets(index)
    positive_sets << initial_set if !initial_is_complete || positive_sets.empty?

    negative = negative_sets.shift || Set.new
    if negative_sets.size > 1
        negative = negative.dup
        negative_sets.each { |set| negative.merge(set) }
    end

    positive_sets = positive_sets.sort_by(&:size)

    result = Set.new
    result.compare_by_identity
    positive_sets.shift.each do |obj|
        result.add(obj) if !negative.include?(obj) && positive_sets.all? { |set| set.include?(obj) }
    end
    return result
end

#handle_parent_child_arguments(other_query, relation, relation_options) ⇒ Object

Helper method for #with_child and #with_parent



168
169
170
# File 'lib/roby/queries/plan_object_matcher.rb', line 168

def handle_parent_child_arguments(other_query, relation, relation_options) # :nodoc:
    return relation, [other_query.match, relation_options]
end

#handle_parent_child_match(object, match_spec) ⇒ Object

Helper method for handling parent/child matches in #===



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/roby/queries/plan_object_matcher.rb', line 224

def handle_parent_child_match(object, match_spec) # :nodoc:
    relation, matchers = *match_spec
    return false if !relation && object.relations.empty?
    for match_spec in matchers
        m, relation_options = *match_spec
        if relation
            if !yield(relation, m, relation_options)
                return false 
            end
        else
            result = object.relations.any? do |rel|
                yield(rel, m, relation_options)
            end
            return false if !result
        end
    end
    true
end

#indexed_query?Boolean

Returns true if filtering with this TaskMatcher using #=== is equivalent to calling #filter() using a Index. This is used to avoid an explicit O(N) filtering step after filter() has been called



246
247
248
# File 'lib/roby/queries/plan_object_matcher.rb', line 246

def indexed_query?
    @indexed_query
end

#indexed_sets(index) ⇒ (Set,Set)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resolve the indexed sets needed to filter an initial set in #filter



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/roby/queries/plan_object_matcher.rb', line 309

def indexed_sets(index)
    positive_sets = []
    for m in @model
        positive_sets << index.by_model[m]
    end

    for o in @owners
        if candidates = index.by_owner[o]
            positive_sets << candidates
        else
            return [Set.new, Set.new]
        end
    end

    for pred in @indexed_predicates
        positive_sets << index.by_predicate[pred]
    end

    negative_sets = @indexed_neg_predicates.
        map { |pred| index.by_predicate[pred] }

    return positive_sets, negative_sets
end

#not_self_ownedObject

Filters out locally-owned tasks

Matches if the object is owned by the local plan manager.



98
99
100
101
# File 'lib/roby/queries/plan_object_matcher.rb', line 98

def not_self_owned
    neg_predicates << :self_owned?
    self
end

#owned_by(*ids) ⇒ Object

Filters on ownership

Matches if the object is owned by the listed peers.

Use #self_owned to match if it is owned by the local plan manager.



82
83
84
85
# File 'lib/roby/queries/plan_object_matcher.rb', line 82

def owned_by(*ids)
    @owners |= ids
    self
end

#self_ownedObject

Filters locally-owned tasks

Matches if the object is owned by the local plan manager.



90
91
92
93
# File 'lib/roby/queries/plan_object_matcher.rb', line 90

def self_owned
    predicates << :self_owned?
    self
end

#to_sObject



250
251
252
253
254
255
256
257
258
259
260
# File 'lib/roby/queries/plan_object_matcher.rb', line 250

def to_s
    description = 
        if instance
            instance.to_s
        elsif model.size == 1
            model.first.to_s
        else
            "(#{model.map(&:to_s).join(",")})"
        end
    ([description] + predicates.map(&:to_s) + neg_predicates.map { |p| "not_#{p}" }).join(".")
end

#with_child(other_query, relation = nil, relation_options = nil) ⇒ Object

Filters based on the object’s children

Matches if this object has at least one child which matches query.

If relation is given, then only the children in this relation are considered. Moreover, relation options can be used to restrict the search even more.

Examples:

parent.depends_on(child)
TaskMatcher.new.
    with_child(TaskMatcher.new.pending) === parent # => true
TaskMatcher.new.
    with_child(TaskMatcher.new.pending, Roby::TaskStructure::Dependency) === parent # => true
TaskMatcher.new.
    with_child(TaskMatcher.new.pending, Roby::TaskStructure::PlannedBy) === parent # => false

TaskMatcher.new.
    with_child(TaskMatcher.new.pending,
               Roby::TaskStructure::Dependency,
               roles: ["trajectory_following"]) === parent # => false
parent.depends_on child, role: "trajectory_following"
TaskMatcher.new.
    with_child(TaskMatcher.new.pending,
               Roby::TaskStructure::Dependency,
               roles: ["trajectory_following"]) === parent # => true


200
201
202
203
204
205
# File 'lib/roby/queries/plan_object_matcher.rb', line 200

def with_child(other_query, relation = nil, relation_options = nil)
    relation, spec = handle_parent_child_arguments(other_query, relation, relation_options)
    (@children[relation] ||= Array.new) << spec
    @indexed_query = false
    self
end

#with_instance(instance) ⇒ Object

Match an instance explicitely



71
72
73
74
75
# File 'lib/roby/queries/plan_object_matcher.rb', line 71

def with_instance(instance)
    @instance = instance
    @indexed_query = false
    self
end

#with_model(model) ⇒ Object

Filters on the task model

Will match if the task is an instance of model or one of its subclasses.



107
108
109
110
# File 'lib/roby/queries/plan_object_matcher.rb', line 107

def with_model(model)
    @model = Array(model)
    self
end

#with_parent(other_query, relation = nil, relation_options = nil) ⇒ Object

Filters based on the object’s parents

Matches if this object has at least one parent which matches query.

If relation is given, then only the parents in this relation are considered. Moreover, relation options can be used to restrict the search even more.

See examples for #with_child



216
217
218
219
220
221
# File 'lib/roby/queries/plan_object_matcher.rb', line 216

def with_parent(other_query, relation = nil, relation_options = nil)
    relation, spec = handle_parent_child_arguments(other_query, relation, relation_options)
    (@parents[relation] ||= Array.new) << spec
    @indexed_query = false
    self
end