File: PropertySet.rb

package info (click to toggle)
tj3 3.8.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,048 kB
  • sloc: ruby: 36,481; javascript: 1,113; sh: 19; makefile: 17
file content (317 lines) | stat: -rw-r--r-- 10,026 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = PropertySet.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
#               by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#

require 'taskjuggler/AttributeDefinition'
require 'taskjuggler/PropertyTreeNode'

class TaskJuggler

  # A PropertySet is a collection of properties of the same kind. Properties can
  # be Task, Resources, Scenario, Shift or Accounts objects. All properties of
  # the same kind belong to the same PropertySet. A property may only belong to
  # one PropertySet in the Project. The PropertySet holds the definitions for
  # the attributes. All Properties of the set will have a set of these
  # attributes.
  class PropertySet

    attr_reader :project, :flatNamespace, :attributeDefinitions

    def initialize(project, flatNamespace)
      if $DEBUG && project.nil?
        raise "project parameter may not be NIL"
      end
      # Indicates whether the namespace of this PropertySet is flat or not. In a
      # flat namespace all property IDs must be unique. Otherwise only the IDs
      # within a group of siblings must be unique. The full ID of the Property
      # is then composed of the siblings ID prefixed by the parent ID. ID fields
      # are separated by dots.
      @flatNamespace = flatNamespace
      # The main Project data structure reference.
      @project = project
      # A list of all PropertyTreeNodes in this set.
      @properties = Array.new
      # A hash of all PropertyTreeNodes in this set, hashed by their ID. This is
      # the same data as in @properties, but hashed by ID for faster access.
      @propertyMap = Hash.new

      # This is the blueprint for PropertyTreeNode attribute sets. Whever a new
      # PropertTreeNode is created, an attribute is created for each definition
      # in this list.
      @attributeDefinitions = Hash.new
      [
        [ 'id',   'ID',       StringAttribute, false,  false,  false,  '' ],
        [ 'name', 'Name',     StringAttribute, false,  false,  false,  '' ],
        [ 'seqno', 'Seq. No', IntegerAttribute, false,  false,  false,  0 ]
      ].each { |a| addAttributeType(AttributeDefinition.new(*a)) }
    end

    # Use the function to declare the various attributes that properties of this
    # PropertySet can have. The attributes must be declared before the first
    # property is added to the set.
    def addAttributeType(attributeType)
      if !@properties.empty?
        raise "Fatal Error: Attribute types must be defined before " +
              "properties are added."
      end

      @attributeDefinitions[attributeType.id] = attributeType
    end

    # Iterate over all attribute definitions.
    def eachAttributeDefinition
      @attributeDefinitions.sort.each do |key, value|
        yield(value)
      end
    end

    # Return true if there is an AttributeDefinition for _attrId_.
    def knownAttribute?(attrId)
      @attributeDefinitions.include?(attrId)
    end

    # Check whether the PropertyTreeNode has a calculated attribute with the
    # ID _attrId_. For scenarioSpecific attributes _scenarioIdx_ needs to be
    # provided.
    def hasQuery?(attrId, scenarioIdx = nil)
      return false if @properties.empty?

      property = @properties.first
      methodName = 'query_' + attrId
      # First we check for non-scenario-specific query functions.
      if property.respond_to?(methodName)
        return true
      elsif scenarioIdx
        # Then we check for scenario-specific ones via the @data member.
        return property.data[scenarioIdx].respond_to?(methodName)
      end
      false
    end

    # Return whether the attribute with _attrId_ is scenario specific or not.
    def scenarioSpecific?(attrId)
      if @attributeDefinitions[attrId]
        # Check the 'scenarioSpecific' flag of the attribute definition.
        @attributeDefinitions[attrId].scenarioSpecific
      elsif (property = @properties.first) &&
            property && property.data &&
            property.data[0].respond_to?("query_#{attrId}")
        # We've found a query_ function for the attrId that is scenario
        # specific.
        true
      else
        # All hardwired, non-existing and non-scenario-specific query_
        # candidates.
        false
      end
    end

    # Return whether the attribute with _attrId_ is inherited from the global
    # scope.
    def inheritedFromProject?(attrId)
      # All hardwired attributes are not inherited.
      return false if @attributeDefinitions[attrId].nil?

      @attributeDefinitions[attrId].inheritedFromProject
    end

    # Return whether the attribute with _attrId_ is inherited from parent.
    def inheritedFromParent?(attrId)
      # All hardwired attributes are not inherited.
      return false if @attributeDefinitions[attrId].nil?

      @attributeDefinitions[attrId].inheritedFromParent
    end

    # Return whether or not the attribute was user defined.
    def userDefined?(attrId)
      return false if @attributeDefinitions[attrId].nil?

      @attributeDefinitions[attrId].userDefined
    end

    def listAttribute?(attrId)
      (ad = @attributeDefinitions[attrId]) && ad.objClass.isList?
    end

    # Return the default value of the attribute.
    def defaultValue(attrId)
      return nil if @attributeDefinitions[attrId].nil?

      @attributeDefinitions[attrId].default
    end

    # Returns the name (human readable description) of the attribute with the
    # Id specified by _attrId_.
    def attributeName(attrId)
      # Some attributes are hardwired into the properties. These need to be
      # treated separately.
      if @attributeDefinitions.include?(attrId)
        return @attributeDefinitions[attrId].name
      end

      nil
    end

    # Return the type of the attribute with the Id specified by _attrId_.
    def attributeType(attrId)
      # Hardwired attributes need special treatment.
      if @attributeDefinitions.has_key?(attrId)
        @attributeDefinitions[attrId].objClass
      else
        nil
      end
    end

    # Add the new PropertyTreeNode object _property_ to the set. The set is
    # indexed by ID. In case an object with the same ID already exists in the
    # set it will be overwritten.
    #
    # Whenever the set has been extended, the 'bsi' and 'tree' attributes of the
    # properties are no longer up-to-date. You must call index() before using
    # these attributes.
    def addProperty(property)
      # The PropertyTreeNode objects are indexed by ID or hierachical ID
      # depending on the name space setting of this set.
      @propertyMap[property.id] = property
      @properties << property
    end

    # Remove the PropertyTreeNode (and all its children) object from the set.
    # _prop_ can either be a property ID or a reference to the PropertyTreeNode.
    #
    # TODO: This function does not take care of references to this PTN!
    def removeProperty(prop)
      if prop.is_a?(String)
        property = @propertyMap[prop]
      else
        property = prop
      end

      # Iterate over all properties and eliminate references to this the
      # PropertyTreeNode to be removed.
      @properties.each do |p|
        p.removeReferences(p)
      end

      # Recursively remove all sub-nodes. The children list is modified during
      # the call, so we can't use an iterator here.
      until property.children.empty? do
        removeProperty(property.children.first)
      end

      @properties.delete(property)
      @propertyMap.delete(property.fullId)

      # Remove this node from the child list of the parent node.
      property.parent.children.delete(property) if property.parent


      property
    end

    # Call this function to delete all registered properties.
    def clearProperties
      @properties.clear
      @propertyMap.clear
    end

    # Return the PropertyTreeNode object with ID _id_ from the set or nil if not
    # present.
    def [](id)
      @propertyMap[id]
    end

    # Update the breakdown structure indicies (bsi). This method needs to
    # be called whenever the set has been modified.
    def index
      each do |p|
        bsIdcs = p.getBSIndicies
        bsi = ""
        first = true
        bsIdcs.each do |idx|
          if first
            first = false
          else
            bsi += '.'
          end
          bsi += idx.to_s
        end
        p.force('bsi', bsi)
      end
    end

    # Return the index of the top-level _property_ in the set.
    def levelSeqNo(property)
      seqNo = 1
      @properties.each do |p|
        unless p.parent
          return seqNo if p == property
          seqNo += 1
        end
      end
      raise "Fatal Error: Unknow property #{property}"
    end

    # Return the maximum used number of breakdown levels. A flat list has a
    # maxDepth of 1. A list with one sub level has a maxDepth of 2 and so on.
    def maxDepth
      md = 0
      each do |p|
        md = p.level if p.level > md
      end
      md + 1
    end

    # Return the number of PropertyTreeNode objects in this set.
    def items
      @properties.length
    end

    alias length items


    # Return true if the set is empty.
    def empty?
      @properties.empty?
    end

    # Return the number of top-level PropertyTreeNode objects. Top-Level items
    # are no children.
    def topLevelItems
      items = 0
      @properties.each do |p|
        items += 1 unless p.parent
      end
      items
    end

    # Iterator over all PropertyTreeNode objects in this set.
    def each
      @properties.each do |value|
        yield(value)
      end
    end

    # Return the set of PropertyTreeNode objects as flat Array.
    def to_ary
      @properties.dup
    end

    def to_s
      PropertyList.new(self).to_s
    end

  end

end