The PropertyList is a utility class that can be used to hold a list of properties. It’s derived from an Array, so it can hold the properties in a well defined order. The order can be determined by an arbitrary number of sorting levels. A sorting level specifies an attribute who’s value should be used for sorting, a scenario index if necessary and the sorting direction (up/down). All nodes in the PropertyList must belong to the same PropertySet.
A PropertyList is always bound to a certain PropertySet. All properties in the list must be of that set.
# File lib/PropertyList.rb, line 30 30: def initialize(arg, copyItems = true) 31: @items = copyItems ? arg.to_ary : [] 32: if arg.is_a?(PropertySet) 33: # Create a PropertyList from the given PropertySet. 34: @propertySet = arg 35: # To keep the list sorted, we may have to access Property attributes. 36: # Pre-scheduling, we can only use static attributes. Post-scheduling, 37: # we can include dynamic attributes as well. This query template will 38: # be used to query attributes when it has been set. Otherwise the list 39: # can only be sorted by static attributes. 40: @query = nil 41: resetSorting 42: addSortingCriteria('seqno', true, 1) 43: sort! 44: else 45: # Create a PropertyList from a given other PropertyList. 46: @propertySet = arg.propertySet 47: @query = arg.query ? arg.query.dup : nil 48: @sortingLevels = arg.sortingLevels 49: @sortingCriteria = arg.sortingCriteria.dup 50: @sortingUp = arg.sortingUp.dup 51: @scenarioIdx = arg.scenarioIdx.dup 52: end 53: end
Append a new sorting level to the existing levels.
# File lib/PropertyList.rb, line 98 98: def addSortingCriteria(criteria, up, scIdx) 99: unless @propertySet.knownAttribute?(criteria) || 100: @propertySet.hasQuery?(criteria, scIdx) 101: raise TjException.new, "Unknown attribute #{criteria} used for " + 102: 'sorting criterium' 103: end 104: if scIdx == 1 105: if @propertySet.scenarioSpecific?(criteria) 106: raise TjException.new, "Attribute #{criteria} is scenario specific." + 107: "You must specify a scenario id." 108: end 109: else 110: if @propertySet.project.scenario(scIdx).nil? 111: raise TjException.new, "Unknown scenario index #{scIdx} used." 112: end 113: if !@propertySet.scenarioSpecific?(criteria) 114: raise TjException.new, "Attribute #{criteria} is not scenario " + 115: "specific" 116: end 117: end 118: @sortingCriteria.push(criteria) 119: @sortingUp.push(up) 120: @scenarioIdx.push(scIdx) 121: @sortingLevels += 1 122: end
Append another Array of PropertyTreeNodes or a PropertyList to this. The list will be sorted again.
# File lib/PropertyList.rb, line 84 84: def append(list) 85: if $DEBUG 86: list.each do |node| 87: unless node.propertySet == @propertySet 88: raise "Fatal Error: All nodes must belong to the same PropertySet." 89: end 90: end 91: end 92: 93: @items.concat(list) 94: sort! 95: end
This function sets the index attribute of all the properties in the list. The index starts with 0 and increases for each property.
# File lib/PropertyList.rb, line 173 173: def index 174: i = 0 175: @items.each do |p| 176: p.set('index', i += 1) 177: end 178: end
Return the Array index of item or nil.
# File lib/PropertyList.rb, line 167 167: def itemIndex(item) 168: @items.index(item) 169: end
This class should be a derived class of Array. But since it re-defines sort!() and still needs to call Array::sort!() I took a different route. All missing methods will be propagated to the @items Array.
# File lib/PropertyList.rb, line 58 58: def method_missing(func, *args, &block) 59: @items.method(func).call(*args, &block) 60: end
Clear all sorting levels.
# File lib/PropertyList.rb, line 75 75: def resetSorting 76: @sortingLevels = 0 77: @sortingCriteria = [] 78: @sortingUp = [] 79: @scenarioIdx = [] 80: end
Set all sorting levels as Array of triplets.
# File lib/PropertyList.rb, line 67 67: def setSorting(modes) 68: resetSorting 69: modes.each do |mode| 70: addSortingCriteria(*mode) 71: end 72: end
Sort the properties according to the currently defined sorting criteria.
# File lib/PropertyList.rb, line 132 132: def sort! 133: if treeMode? 134: # Tree sorting is somewhat complex. It will be based on the 'tree' 135: # attribute of the PropertyTreeNodes but we have to update them first 136: # based on the other sorting criteria. 137: 138: # Remove the tree sorting mode first. 139: sc = @sortingCriteria.delete_at(0) 140: su = @sortingUp.delete_at(0) 141: si = @scenarioIdx.delete_at(0) 142: @sortingLevels -= 1 143: 144: # Sort the list based on the rest of the modes. 145: sortInternal 146: # The update the 'index' attributes of the PropertyTreeNodes. 147: index 148: # An then the 'tree' attributes. 149: indexTree 150: 151: # Restore the 'tree' sorting mode again. 152: @sortingCriteria.insert(0, sc) 153: @sortingUp.insert(0, su) 154: @scenarioIdx.insert(0, si) 155: @sortingLevels += 1 156: 157: # Sort again, now based on the updated 'tree' attributes. 158: sortInternal 159: else 160: sortInternal 161: end 162: # Update indexes. 163: index 164: end
# File lib/PropertyList.rb, line 62 62: def to_ary 63: @items.dup 64: end
If the first sorting level is ‘tree’ the breakdown structure of the list is preserved. This is a somewhat special mode and this function returns true if the mode is set.
# File lib/PropertyList.rb, line 127 127: def treeMode? 128: @sortingLevels > 0 && @sortingCriteria[0] == 'tree' 129: end
Update the ‘tree’ indicies that are needed for the ‘tree’ sorting mode.
# File lib/PropertyList.rb, line 195 195: def indexTree 196: @items.each do |property| 197: # The indicies are an Array if the 'index' attributes for this 198: # property and all its parents. 199: treeIdcs = property.getIndicies 200: # Now convert them to a String. 201: tree = '' 202: treeIdcs.each do |idx| 203: # Prefix the level index with zeros so that we always have a 6 204: # digit long String. 6 digits should be large enough for all 205: # real-world projects. 206: tree += idx.to_s.rjust(6, '0') 207: end 208: property.set('tree', tree) 209: end 210: end
# File lib/PropertyList.rb, line 212 212: def sortInternal 213: @items.sort! do |a, b| 214: res = 0 215: @sortingLevels.times do |i| 216: if @query 217: # In case we have a Query reference, we get the two values with this 218: # query. 219: @query.scenarioIdx = @scenarioIdx[i] < 0 ? nil : @scenarioIdx[i] 220: @query.attributeId = @sortingCriteria[i] 221: 222: @query.property = a 223: @query.process 224: aVal = @query.to_sort 225: 226: @query.property = b 227: @query.process 228: bVal = @query.to_sort 229: else 230: # In case we don't have a query, we use the static mechanism. 231: # If the scenario index is negative we have a non-scenario-specific 232: # attribute. 233: if @scenarioIdx[i] < 0 234: if @sortingCriteria[i] == 'id' 235: aVal = a.fullId 236: bVal = b.fullId 237: else 238: aVal = a.get(@sortingCriteria[i]) 239: bVal = b.get(@sortingCriteria[i]) 240: end 241: else 242: aVal = a[@sortingCriteria[i], @scenarioIdx[i]] 243: bVal = b[@sortingCriteria[i], @scenarioIdx[i]] 244: end 245: end 246: res = aVal <=> bVal 247: # Invert the result if we have to sort in decreasing order. 248: res = -res unless @sortingUp[i] 249: # If the two elements are equal on this compare level we try the next 250: # level. 251: break if res != 0 252: end 253: res 254: end 255: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.