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
|