Class TaskJuggler::PropertyTreeNode
In: lib/taskjuggler/PropertyTreeNode.rb
Parent: Object

This class is the base object for all Project properties. A Project property is a e. g. a Task, a Resource or other objects. Such properties can be arranged in tree form by assigning child properties to an existing property. The parent object needs to exist at object creation time. The PropertyTreeNode class holds all data and methods that are common to the different types of properties. Each property can have a set of predifined attributes. The PropertySet class holds collections of the same PropertyTreeNode objects and the defined attributes. Each PropertySet has a predefined set of attributes, but the attribute set can be extended by the user. E.g. a task has the predefined attribute ‘start’ and ‘end’ date. The user can extend tasks with a user defined attribute like an URL that contains more details about the task.

Methods

Attributes

adoptees  [R] 
children  [R] 
data  [R] 
id  [R] 
name  [RW] 
parent  [R] 
project  [R] 
propertySet  [R] 
sequenceNo  [R] 
sourceFileInfo  [RW] 
subId  [R] 

Public Class methods

Create a new PropertyTreeNode object. propertySet is the PropertySet that this PropertyTreeNode object belongs to. The PropertySet determines the attributes that are common to all Nodes in the set. id is a String that is unique in the namespace of the set. name is a user readable, short description of the object. parent is the PropertyTreeNode that sits above this node in the object hierachy. A root object has a parent of nil. For sets with hierachical name spaces, parent can be nil and specified by a hierachical id (e. g. ‘father.son’).

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 43
    def initialize(propertySet, id, name, parent)
      @propertySet = propertySet
      @project = propertySet.project
      @parent = parent

      # Scenario specific data
      @data = nil

      # Attributes are created on-demand. We need to be careful that a pure
      # check for existance does not create them unecessarily.
      @attributes = Hash.new do |hash, attributeId|
        unless (aType = attributeDefinition(attributeId))
          raise ArgumentError,
            "Unknown attribute '#{attributeId}' requested for " +
            "#{self.class.to_s.sub(/TaskJuggler::/, '')} '#{fullId}'"
        end
        unless aType.scenarioSpecific
          hash[attributeId] = aType.objClass.new(@propertySet, aType, self)
        else
          raise ArgumentError, "Attribute '#{attributeId}' is scenario specific"
        end
      end
      @scenarioAttributes = Array.new(@project.scenarioCount) do |scenarioIdx|
        Hash.new do |hash, attributeId|
          unless (aType = attributeDefinition(attributeId))
            raise ArgumentError,
              "Unknown attribute '#{attributeId}' requested for " +
              "#{self.class.to_s.sub(/TaskJuggler::/, '')} '#{fullId}'"
          end
          if aType.scenarioSpecific
            hash[attributeId] = aType.objClass.new(@propertySet, aType,
                                                   @data[scenarioIdx])
          else
            raise ArgumentError,
              "Attribute '#{attributeId}' is not scenario specific"
          end
        end
      end

      # If _id_ is still nil, we generate a unique id.
      unless id
        tag = self.class.to_s.gsub(/TaskJuggler::/, '')
        id = '_' + tag + '_' + (propertySet.items + 1).to_s
        id = parent.fullId + '.' + id if !@propertySet.flatNamespace && parent
      end
      if !@propertySet.flatNamespace && id.include?('.')
        parentId = id[0..(id.rindex('.') - 1)]
        # Set parent to the parent property if it's still nil.
        @parent = @propertySet[parentId] unless @parent
        if $DEBUG
          if !@parent || !@propertySet[@parent.fullId]
            raise "Fatal Error: parent must be member of same property set"
          end
          if parentId != @parent.fullId
            raise "Fatal Error: parent (#{@parent.fullId}) and parent ID " +
                "(#{@parentId}) don't match"
          end
        end
        @subId = id[(id.rindex('.') + 1).. -1]
      else
        @subId = id
      end
      # The attribute 'id' is either the short ID or the full hierarchical ID.
      set('id', fullId)

      # The name of the property.
      @name = name
      set('name', name)

      @level = -1
      @sourceFileInfo = nil

      @sequenceNo = @propertySet.items + 1
      set('seqno', @sequenceNo)
      # This is a list of the real sub nodes of this PropertyTreeNode.
      @children = []
      # This is a list of the adopted sub nodes of this PropertyTreeNode.
      @adoptees = []
      # In case we have a parent object, we register this object as child of
      # the parent.
      if (@parent)
        @parent.addChild(self)
      end
      # This is a list of the PropertyTreeNode objects that have adopted this
      # node.
      @stepParents = []
    end

Public Instance methods

Return the value of the attribute with ID attributeId. For scenario-specific attributes, scenario must indicate the index of the Scenario.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 458
    def [](attributeId, scenario)
      @scenarioAttributes[scenario][attributeId]
      @data[scenario].instance_variable_get(('@' + attributeId).intern)
    end

Set the scenario specific attribute with ID attributeId for the scenario with index scenario to value. If scenario is nil, the attribute must not be scenario specific. In case the attribute does not exist, an exception is raised.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 421
    def []=(attributeId, scenario, value)
      if scenario
        if AttributeBase.mode == 0
          # If we get values in 'provided' mode, we copy them immedidately to
          # all derived scenarios.
          overwrite = false
          @project.scenario(scenario).all.each do |sc|
            scenarioIdx = @project.scenarioIdx(sc)

            if @scenarioAttributes[scenarioIdx][attributeId].provided
              overwrite = true
            end

            if scenarioIdx == scenario
              @scenarioAttributes[scenarioIdx][attributeId].set(value)
            else
              @scenarioAttributes[scenarioIdx][attributeId].inherit(value)
            end
          end
          # We only raise the overwrite error after all scenarios have been
          # set. For some attributes the overwrite is actually allowed.
          if overwrite
            raise AttributeOverwrite,
              "Overwriting a previously provided value for attribute " +
              "#{attributeId}"
          end
        else
          @scenarioAttributes[scenario][attributeId].set(value)
        end
      else
        @attributes[attributeId].set(value)
      end
    end

Add child node as child to this node.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 339
    def addChild(child)
      if $DEBUG && child.propertySet != @propertySet
        raise "Child nodes must belong to the same property set as the parent"
      end
      @children.push(child)
    end

Adopt property as a step child. Also register the new relationship with the child.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 149
    def adopt(property)
      # A property cannot adopt itself.
      if self == property
        error('adopt_self', 'A property cannot adopt itself')
      end

      # A top level task must never contain the same leaf task more then once!
      allOfRoot = root.all
      property.allLeaves.each do |adoptee|
        if allOfRoot.include?(adoptee)
          error('adopt_duplicate_child',
                "The task '#{adoptee.fullId}' has already been adopted by " +
                "property '#{root.fullId}' or any of its sub-properties.")
        end
      end

      @adoptees << property
      property.getAdopted(self)
    end

Returns a list of this node and all transient sub nodes.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 261
    def all
      res = [ self ]
      kids.each do |c|
        res = res.concat(c.all)
      end
      res
    end

Return a list of all leaf nodes of this node.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 270
    def allLeaves(withoutSelf = false)
      res = []
      if leaf?
        res << self unless withoutSelf
      else
        kids.each do |c|
          res += c.allLeaves
        end
      end
      res
    end

Return a list with all parent nodes of this node.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 366
    def ancestors
      nodes = []
      n = self
      while n.parent
        nodes << (n = n.parent)
      end
      nodes
    end

Return the type of the attribute with ID attributeId.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 385
    def attributeDefinition(attributeId)
      @propertySet.attributeDefinitions[attributeId]
    end

This method creates a shallow copy of all attributes and returns them as an Array that can be used with restoreAttributes().

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 190
    def backupAttributes
      [ @attributes.clone, @scenarioAttributes.clone ]
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 505
    def checkFailsAndWarnings
      if @attributes.has_key?('fail') || @attributes.has_key?('warn')
        propertyType = case self
                       when Task
                         'task'
                       when Resource
                         'resource'
                       else
                         'unknown'
                       end
        queryAttrs = { 'project' => @project,
                       'property' => self,
                       'scopeProperty' => nil,
                       'start' => @project['start'],
                       'end' => @project['end'],
                       'loadUnit' => :days,
                       'numberFormat' => @project['numberFormat'],
                       'timeFormat' => nil,
                       'currencyFormat' => @project['currencyFormat'] }
        query = Query.new(queryAttrs)
        if @attributes['fail']
          @attributes['fail'].get.each do |expr|
            if expr.eval(query)
              error("#{propertyType}_fail_check",
                    "User defined check failed for #{propertyType} " +
                    "#{fullId} \n" +
                    "Condition: #{expr.to_s}\n" +
              "Result:    #{expr.to_s(query)}")
            end
          end
        end
        if @attributes['warn']
          @attributes['warn'].get.each do |expr|
            if expr.eval(query)
              warning("#{propertyType}_warn_check",
                      "User defined warning triggered for #{propertyType} " +
                      "#{fullId} \n" +
                      "Condition: #{expr.to_s}\n" +
              "Result:    #{expr.to_s(query)}")
            end
          end
        end
      end
    end

Return true if the node has children.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 361
    def container?
      !@children.empty? || !@adoptees.empty?
    end

We only use deep_clone for attributes, never for properties. Since attributes may reference properties these references should remain references.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 134
    def deep_clone
      self
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 656
    def error(id, text)
      @project.messageHandler.error(id, text, @sourceFileInfo, nil, self, nil)
    end

Return the full id of this node. For PropertySet objects with a flat namespace, this is just the ID. Otherwise, the full ID is composed of all IDs from the root node to this node, separating the IDs by a dot.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 285
    def fullId
      res = @subId
      unless @propertySet.flatNamespace
        t = self
        until (t = t.parent).nil?
          res = t.subId + "." + res
        end
      end
      res
    end

Return the value of the non-scenario-specific attribute with ID attributeId. This method works for built-in attributes as well. In case the attribute does not exist, an exception is raised.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 392
    def get(attributeId)
      # Make sure the attribute gets created if it doesn't exist already.
      @attributes[attributeId]
      instance_variable_get(('@' + attributeId).intern)
    end

Return the value of the attribute with ID attributeId. This method works for built-in attributes as well. In case this is a scenario-specific attribute, the scenario index needs to be provided by scenarioIdx, otherwise it must be nil. In case the attribute does not exist, an exception is raised.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 403
    def getAttribute(attributeId, scenarioIdx = nil)
      if scenarioIdx
        @scenarioAttributes[scenarioIdx][attributeId]
      else
        @attributes[attributeId]
      end
    end

Return the hierarchical index of this node. In project management lingo this is called the Breakdown Structure Index (BSI). The result is an Array with an index for each level from the root to this node.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 313
    def getBSIndicies
      idcs = []
      p = self
      begin
        parent = p.parent
        idcs.insert(0, parent ? parent.levelSeqNo(p) :
                                @propertySet.levelSeqNo(p))
        p = parent
      end while p
      idcs
    end

Return the ‘index’ attributes of this property, prefixed by the ‘index’ attributes of all its parents. The result is an Array of Fixnums.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 327
    def getIndicies
      idcs = []
      p = self
      begin
        parent = p.parent
        idcs.insert(0, p.get('index'))
        p = parent
      end while p
      idcs
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 664
    def info(id, text)
      @project.messageHandler.info(id, text, @sourceFileInfo, nil, self, nil)
    end

Inherit values for the attributes from the parent node or the Project.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 213
    def inheritAttributes
      # Inherit non-scenario-specific values
      @propertySet.eachAttributeDefinition do |attrDef|
        next if attrDef.scenarioSpecific || !attrDef.inheritedFromParent

        aId = attrDef.id
        if parent
          # Inherit values from parent property
          if parent.provided(aId) || parent.inherited(aId)
            @attributes[aId].inherit(parent.get(aId))
          end
        else
          # Inherit selected values from project if top-level property
          if attrDef.inheritedFromProject
            if @project[aId]
              @attributes[aId].inherit(@project[aId])
            end
          end
        end
      end

      # Inherit scenario-specific values
      @propertySet.eachAttributeDefinition do |attrDef|
        next if !attrDef.scenarioSpecific || !attrDef.inheritedFromParent

        @project.scenarioCount.times do |scenarioIdx|
          if parent
            # Inherit scenario specific values from parent property
            if parent.provided(attrDef.id, scenarioIdx) ||
               parent.inherited(attrDef.id, scenarioIdx)
              @scenarioAttributes[scenarioIdx][attrDef.id].inherit(
                  parent[attrDef.id, scenarioIdx])
            end
          else
            # Inherit selected values from project if top-level property
            if attrDef.inheritedFromProject
              if @project[attrDef.id] &&
                 @scenarioAttributes[scenarioIdx][attrDef.id]
                @scenarioAttributes[scenarioIdx][attrDef.id].inherit(
                    @project[attrDef.id])
              end
            end
          end
        end
      end
    end

Returns true if the value of the attribute attributeId (in scenario scenarioIdx) has been inherited from a parent node or scenario.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 479
    def inherited(attributeId, scenarioIdx = nil)
      if scenarioIdx
        unless @scenarioAttributes[scenarioIdx].has_key?(attributeId)
          return false
        end
        @scenarioAttributes[scenarioIdx][attributeId].inherited
      else
        return false unless @attributes.has_key?(attributeId)
        @attributes[attributeId].inherited
      end
    end

Find out if this property is a direct or indirect child of ancestor.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 347
    def isChildOf?(ancestor)
      parent = self
      while parent = parent.parent
        return true if (parent == ancestor)
      end
      false
    end

Return a list of all children including adopted kids.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 179
    def kids
      @children + @adoptees
    end

Return true if the node is a leaf node (has no children).

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 356
    def leaf?
      @children.empty? && @adoptees.empty?
    end

Returns the level that this property is on. Top-level properties return 0, their children 1 and so on. This value is cached internally, so it does not have to be calculated each time the function is called.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 299
    def level
      return @level if @level >= 0

      t = self
      @level = 0
      until (t = t.parent).nil?
        @level += 1
      end
      @level
    end

Return the index of the child node.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 208
    def levelSeqNo(node)
      @children.index(node) + 1
    end

Many PropertyTreeNode functions are scenario specific. These functions are provided by the class *Scenario classes. In case we can‘t find a function called for the base class we try to find it in corresponding *Scenario class.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 652
    def method_missing(func, scenarioIdx, *args, &block)
      @data[scenarioIdx].send(func, *args, &block)
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 491
    def modified?(attributeId, scenarioIdx = nil)
      if scenarioIdx
        unless @scenarioAttributes[scenarioIdx].has_key?(attributeId)
          return false
        end

        @scenarioAttributes[scenarioIdx][attributeId].provided ||
        @scenarioAttributes[scenarioIdx][attributeId].inherited
      else
        return false unless @attributes.has_key?(attributeId)
        @attributes[attributeId].provided || @attributes[attributeId].inherited
      end
    end

Return a list of all parents including step parents.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 184
    def parents
      (@parent ? [ @parent ] : []) + @stepParents
    end

Returns true if the value of the attribute attributeId (in scenario scenarioIdx) has been provided by the user.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 465
    def provided(attributeId, scenarioIdx = nil)
      if scenarioIdx
        unless @scenarioAttributes[scenarioIdx].has_key?(attributeId)
          return false
        end
        @scenarioAttributes[scenarioIdx][attributeId].provided
      else
        return false unless @attributes.has_key?(attributeId)
        @attributes[attributeId].provided
      end
    end

We often use PTNProxy objects to represent PropertyTreeNode objects. The proxy usually does a good job acting like a PropertyTreeNode. But in some situations, we want to make sure to operate on the PropertyTreeNode and not the PTNProxy. Both classes provide a ptn() method that always return the PropertyTreeNode.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 143
    def ptn
      self
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 554
    def query_alert(query)
      journal = @project['journal']
      query.sortable = query.numerical = alert =
        journal.alertLevel(query.end, self, query.hideJournalEntry)
      alertLevel = @project['alertLevels'][alert]
      query.string = alertLevel.name
      rText = "<fcol:#{alertLevel.color}><nowiki>#{alertLevel.name}" +
              "</nowiki></fcol>"
      unless (rti = RichText.new(rText, RTFHandlers.create(@project),
                                 @project.messageHandler).
                                 generateIntermediateFormat)
        @project.messageHandler.warning('ptn_journal',
                                        "Syntax error in journal message")
        return nil
      end
      rti.blockMode = false
      query.rti = rti
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 573
    def query_alertmessages(query)
      journalMessages(@project['journal'].alertEntries(query.end, self, 1,
                                                       query.start,
                                                       query.hideJournalEntry),
                      query, true)
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 580
    def query_alertsummaries(query)
      journalMessages(@project['journal'].alertEntries(query.end, self, 1,
                                                       query.start,
                                                       query.hideJournalEntry),
                      query, false)
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 601
    def query_alerttrend(query)
      journal = @project['journal']
      startAlert = journal.alertLevel(query.start, self, query.hideJournalEntry)
      endAlert = journal.alertLevel(query.end, self, query.hideJournalEntry)
      if startAlert < endAlert
        query.sortable = 0
        query.string = 'Up'
      elsif startAlert > endAlert
        query.sortable = 2
        query.string = 'Down'
      else
        query.sortable = 1
        query.string = 'Flat'
      end
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 550
    def query_journal(query)
      @project['journal'].to_rti(query)
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 587
    def query_journalmessages(query)
      journalMessages(@project['journal'].currentEntries(query.end, self, 0,
                                                         query.start,
                                                         query.hideJournalEntry),
                      query, true)
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 594
    def query_journalsummaries(query)
      journalMessages(@project['journal'].currentEntries(query.end, self, 0,
                                                         query.start,
                                                         query.hideJournalEntry),
                      query, false)
    end

Remove any references in the stored data that references the property.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 201
    def removeReferences(property)
      @children.delete(property)
      @adoptees.delete(property)
      @stepParents.delete(property)
    end

Restore the attributes to a previously saved state. backup is an Array generated by backupAttributes().

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 196
    def restoreAttributes(backup)
      @attributes, @scenarioAttributes = backup
    end

Return the top-level node for this node.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 376
    def root
      n = self
      while n.parent
        n = n.parent
      end
      n
    end

Set the non-scenario-specific attribute with ID attributeId to value. In case the attribute does not exist, an exception is raised.

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 413
    def set(attributeId, value)
      @attributes[attributeId].set(value)
    end

[Source]

# File lib/taskjuggler/PropertyTreeNode.rb, line 660
    def warning(id, text)
      @project.messageHandler.warning(id, text, @sourceFileInfo, nil, self, nil)
    end

[Validate]