caruby-core 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +51 -0
  5. data/doc/website/css/site.css +1 -5
  6. data/doc/website/images/avatar.png +0 -0
  7. data/doc/website/images/favicon.ico +0 -0
  8. data/doc/website/images/logo.png +0 -0
  9. data/doc/website/index.html +82 -0
  10. data/doc/website/install.html +87 -0
  11. data/doc/website/quick_start.html +87 -0
  12. data/doc/website/tissue.html +85 -0
  13. data/doc/website/uom.html +10 -0
  14. data/lib/caruby.rb +3 -0
  15. data/lib/caruby/active_support/README.txt +2 -0
  16. data/lib/caruby/active_support/core_ext/string.rb +7 -0
  17. data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
  18. data/lib/caruby/active_support/inflections.rb +55 -0
  19. data/lib/caruby/active_support/inflector.rb +398 -0
  20. data/lib/caruby/cli/application.rb +36 -0
  21. data/lib/caruby/cli/command.rb +169 -0
  22. data/lib/caruby/csv/csv_mapper.rb +157 -0
  23. data/lib/caruby/csv/csvio.rb +185 -0
  24. data/lib/caruby/database.rb +252 -0
  25. data/lib/caruby/database/fetched_matcher.rb +66 -0
  26. data/lib/caruby/database/persistable.rb +432 -0
  27. data/lib/caruby/database/persistence_service.rb +162 -0
  28. data/lib/caruby/database/reader.rb +599 -0
  29. data/lib/caruby/database/saved_merger.rb +131 -0
  30. data/lib/caruby/database/search_template_builder.rb +59 -0
  31. data/lib/caruby/database/sql_executor.rb +75 -0
  32. data/lib/caruby/database/store_template_builder.rb +200 -0
  33. data/lib/caruby/database/writer.rb +469 -0
  34. data/lib/caruby/domain/annotatable.rb +25 -0
  35. data/lib/caruby/domain/annotation.rb +23 -0
  36. data/lib/caruby/domain/attribute_metadata.rb +447 -0
  37. data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
  38. data/lib/caruby/domain/merge.rb +91 -0
  39. data/lib/caruby/domain/properties.rb +95 -0
  40. data/lib/caruby/domain/reference_visitor.rb +289 -0
  41. data/lib/caruby/domain/resource_attributes.rb +528 -0
  42. data/lib/caruby/domain/resource_dependency.rb +205 -0
  43. data/lib/caruby/domain/resource_introspection.rb +159 -0
  44. data/lib/caruby/domain/resource_metadata.rb +117 -0
  45. data/lib/caruby/domain/resource_module.rb +285 -0
  46. data/lib/caruby/domain/uniquify.rb +38 -0
  47. data/lib/caruby/import/annotatable_class.rb +28 -0
  48. data/lib/caruby/import/annotation_class.rb +27 -0
  49. data/lib/caruby/import/annotation_module.rb +67 -0
  50. data/lib/caruby/import/java.rb +338 -0
  51. data/lib/caruby/migration/migratable.rb +167 -0
  52. data/lib/caruby/migration/migrator.rb +533 -0
  53. data/lib/caruby/migration/resource.rb +8 -0
  54. data/lib/caruby/migration/resource_module.rb +11 -0
  55. data/lib/caruby/migration/uniquify.rb +20 -0
  56. data/lib/caruby/resource.rb +969 -0
  57. data/lib/caruby/util/attribute_path.rb +46 -0
  58. data/lib/caruby/util/cache.rb +53 -0
  59. data/lib/caruby/util/class.rb +99 -0
  60. data/lib/caruby/util/collection.rb +1053 -0
  61. data/lib/caruby/util/controlled_value.rb +35 -0
  62. data/lib/caruby/util/coordinate.rb +75 -0
  63. data/lib/caruby/util/domain_extent.rb +49 -0
  64. data/lib/caruby/util/file_separator.rb +65 -0
  65. data/lib/caruby/util/inflector.rb +20 -0
  66. data/lib/caruby/util/log.rb +95 -0
  67. data/lib/caruby/util/math.rb +12 -0
  68. data/lib/caruby/util/merge.rb +59 -0
  69. data/lib/caruby/util/module.rb +34 -0
  70. data/lib/caruby/util/options.rb +92 -0
  71. data/lib/caruby/util/partial_order.rb +36 -0
  72. data/lib/caruby/util/person.rb +119 -0
  73. data/lib/caruby/util/pretty_print.rb +184 -0
  74. data/lib/caruby/util/properties.rb +112 -0
  75. data/lib/caruby/util/stopwatch.rb +66 -0
  76. data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
  77. data/lib/caruby/util/transitive_closure.rb +45 -0
  78. data/lib/caruby/util/tree.rb +48 -0
  79. data/lib/caruby/util/trie.rb +37 -0
  80. data/lib/caruby/util/uniquifier.rb +30 -0
  81. data/lib/caruby/util/validation.rb +48 -0
  82. data/lib/caruby/util/version.rb +56 -0
  83. data/lib/caruby/util/visitor.rb +351 -0
  84. data/lib/caruby/util/weak_hash.rb +36 -0
  85. data/lib/caruby/version.rb +3 -0
  86. metadata +186 -0
@@ -0,0 +1,92 @@
1
+ require 'caruby/util/collection'
2
+ require 'caruby/util/validation'
3
+ require 'caruby/util/merge'
4
+
5
+ # Options is a utility class to support method options.
6
+ class Options
7
+ # Returns the value of option in options as follows:
8
+ # * If options is a hash which contains the option key, then this method returns
9
+ # the option value. A non-collection options[option] value is wrapped as a singleton
10
+ # collection to conform to a collection default type, as shown in the example below.
11
+ # * If options equals the option symbol, then this method returns +true+.
12
+ # * If options is an Array of symbols which includes the given option, then this method
13
+ # returns +true+.
14
+ # * Otherwise, this method returns the default.
15
+ #
16
+ # If default is nil and a block is given to this method, then the default is determined
17
+ # by calling the block with no arguments. The block can also be used to raise a missing
18
+ # option exception, e.g.:
19
+ # Options.get(:userid, options) { raise RuntimeError.new("Missing required option: userid") }
20
+ #
21
+ # @example
22
+ # Options.get(:create, {:create => true}) #=> true
23
+ # Options.get(:create, :create) #=> true
24
+ # Options.get(:create, [:create, :compress]) #=> true
25
+ # Options.get(:create, nil) #=> nil
26
+ # Options.get(:create, nil, :false) #=> false
27
+ # Options.get(:create, nil, :true) #=> true
28
+ # Options.get(:values, nil, []) #=> []
29
+ # Options.get(:values, {:values => :a}, []) #=> [:a]
30
+ def self.get(option, options, default=nil, &block)
31
+ return default(default, &block) if options.nil?
32
+ case options
33
+ when Hash then
34
+ value = options[option]
35
+ value.nil? ? default(default, &block) : value
36
+ when Enumerable then
37
+ options.include?(option) ? true : default(default, &block)
38
+ when Symbol then
39
+ option == options ? true : default(default, &block)
40
+ else
41
+ raise ArgumentError.new("Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
42
+ end
43
+ end
44
+
45
+ # Merges the others options with options and returns the new merged option hash.
46
+ #
47
+ # @example
48
+ # Options.merge(nil, :create) #=> {:create => :true}
49
+ # Options.merge(:create, :optional => :a, :required => :b) #=> {:create => :true, :optional => :a, :required => :b}
50
+ # Options.merge({:required => [:b]}, :required => [:c]) #=> {:required => [:b, :c]}
51
+ def self.merge(options, others)
52
+ options = options.dup if Hash === options
53
+ self.merge!(options, others)
54
+ end
55
+
56
+ # Merges the others options into the given options and returns the created or modified option hash.
57
+ # This method differs from {Options.merge} by modifying an existing options hash.
58
+ def self.merge!(options, others)
59
+ to_hash(options).merge!(to_hash(others)) { |key, oldval, newval| oldval.respond_to?(:merge) ? oldval.merge(newval) : newval }
60
+ end
61
+
62
+ # Returns the options as a hash. If options is already a hash, then this method returns hash.
63
+ # * If options is a Symbol _s_, then this method returns +{+_s_+=>true}+.
64
+ # * An Array of Symbols is enumerated as individual Symbol options.
65
+ # * If options is nil, then this method returns a new empty hash.
66
+ def self.to_hash(options)
67
+ return Hash.new if options.nil?
68
+ case options
69
+ when Hash then
70
+ options
71
+ when Array then
72
+ options.to_hash { |item| Symbol === item or raise ArgumentError.new("Option is not supported; expected Symbol, found: #{options.class}") }
73
+ when Symbol then
74
+ {options => true}
75
+ else
76
+ raise ArgumentError.new("Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
77
+ end
78
+ end
79
+
80
+ # Raises a ValidationError if the given options are not in the given allowable choices.
81
+ def self.validate(options, choices)
82
+ to_hash(options).each_key do |opt|
83
+ raise ValidationError.new("Option is not supported: #{opt}") unless choices.include?(opt)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def self.default(value)
90
+ value.nil? && block_given? ? yield : value
91
+ end
92
+ end
@@ -0,0 +1,36 @@
1
+ # A PartialOrder is a Comparable which restricted scope. Classes wich include PartialOrder
2
+ # are required to implement the <=> operator with the following semantics:
3
+ # * _a_ <=> _b_ returns -1, 0, or 1 if a and b are comparable, nil otherwise
4
+ # A PartialOrder thus relaxes comparison symmetry, e.g.
5
+ # a < b
6
+ # does not imply
7
+ # b >= a.
8
+ # Example:
9
+ # module Queued
10
+ # attr_reader :queue
11
+ # def <=>(other)
12
+ # raise TypeError.new("Comparison argument is not another Queued item") unless Queued == other
13
+ # queue.index(self) <=> queue.index(other) if queue.equal?(other.queue)
14
+ # end
15
+ # end
16
+ # q1 = [a, b] # a, b are Queued
17
+ # q2 = [c] # c is a Queued
18
+ # a < b #=> true
19
+ # b < c #=> nil
20
+ module PartialOrder
21
+ include Comparable
22
+
23
+ Comparable.instance_methods(false).each do |m|
24
+ define_method(m.to_sym) do |other|
25
+ self <=> other ? super : nil
26
+ end
27
+
28
+ # Returns true if other is an instance of this object's class and other == self,
29
+ # false otherwise.
30
+ def eql?(other)
31
+ self.class === other and super
32
+ end
33
+
34
+ alias :== :eql?
35
+ end
36
+ end
@@ -0,0 +1,119 @@
1
+ require 'caruby/util/validation'
2
+
3
+ # Mix-in for standard Person attributes.
4
+ module CaRuby
5
+ module Person
6
+ class Name
7
+ include Validation
8
+
9
+ attr_accessor :salutation, :qualifier, :credentials
10
+ attr_reader :first, :last, :middle
11
+
12
+ # Creates a new Name with the required last and optional first and middle components.
13
+ def initialize(last, first=nil, middle=nil)
14
+ # replace empty with nil
15
+ @first = first unless first == ''
16
+ @last = last unless last == ''
17
+ @middle = middle unless middle == ''
18
+ end
19
+
20
+ # Returns this Name as an array consisting of the first, middle and last fields.
21
+ #
22
+ # @example
23
+ # Person.parse("Abe Lincoln").to_a #=> ["Abe", nil, "Lincoln"]
24
+ def to_a
25
+ [@first, @middle, @last]
26
+ end
27
+
28
+ # Returns this Name in the format [Salutation] First [Middle] Last[, Credentials].
29
+ def to_s
30
+ name_s = [salutation, first, middle, last, qualifier].reject { |part| part.nil? }.join(' ')
31
+ name_s << ', ' << credentials if credentials
32
+ name_s
33
+ end
34
+
35
+ # Returns whether this Person's first, middle and last name components equal the other Person's.
36
+ def ==(other)
37
+ self.class == other.class and first == other.first and middle == other.middle and last == other.last
38
+ end
39
+
40
+ alias :inspect :to_s
41
+
42
+ # Parses the name_s String into a Name. The name can be in one of the following formats:
43
+ # * last, first middle
44
+ # * [salutation] [first [middle]] last [qualifier] [, credentials]
45
+ # where _salutation_ ends in a period.
46
+ #
47
+ # Examples:
48
+ # * Longfellow, Henry Wadsworth
49
+ # * Longfellow, Henry Gallifant Wadsworth
50
+ # * Henry Longfellow
51
+ # * Longfellow
52
+ # * Mr. Henry Wadsworth Longfellow III, MD, Ph.D.
53
+ def self.parse(name_s)
54
+ return if name_s.blank?
55
+ # the name component variables
56
+ first = middle = last = salutation = qualifier = credentials = nil
57
+ # split into comma-delimited tokens
58
+ tokens = name_s.split(',')
59
+ # the word(s) before the first comma
60
+ before_comma = tokens[0].split(' ')
61
+ # if this is a last, first middle format, then parse it that way.
62
+ # otherwise the format is [salutation] [first [middle]] last [qualifier] [credentials]
63
+ if before_comma.size == 1 then
64
+ last = before_comma[0]
65
+ if tokens.size > 1 then
66
+ after_comma = tokens[1].split(' ')
67
+ first = after_comma.shift
68
+ middle = after_comma.join(' ')
69
+ end
70
+ else
71
+ # extract the salutation from the front, if any
72
+ salutation = before_comma.shift if salutation?(before_comma[0])
73
+ # extract the qualifier from the end, if any
74
+ qualifier = before_comma.pop if qualifier?(before_comma[-1])
75
+ # extract the last name from the end
76
+ last = before_comma.pop
77
+ # extract the first name from the front
78
+ first = before_comma.shift
79
+ # the middle name is whatever is left before the comma
80
+ middle = before_comma.join(' ')
81
+ # the credentials are the comma-delimited words after the first comma
82
+ credentials = tokens[1..-1].join(',').strip
83
+ end
84
+ # if there is only one name field, then it is the last name
85
+ if last.nil? then
86
+ last = first
87
+ first = nil
88
+ end
89
+ # make the name
90
+ name = self.new(last, first, middle)
91
+ name.salutation = salutation
92
+ name.qualifier = qualifier
93
+ name.credentials = credentials
94
+ name
95
+ end
96
+
97
+ # Raises ValidationError if there is neither a first nor a last name
98
+ # or if there is a middle name but no first name.
99
+ def validate
100
+ if last.nil? and first.nil? then
101
+ raise ValidationError.new("Name is missing both the first and last fields")
102
+ end
103
+ if !middle.nil? and first.nil? then
104
+ raise ValidationError.new("Name with middle field #{middle} is missing the first field")
105
+ end
106
+ end
107
+
108
+ # Returns whether s ends in a period.
109
+ def self.salutation?(s)
110
+ s =~ /\.$/
111
+ end
112
+
113
+ # Returns whether s is Jr., Sr., II, III, etc.
114
+ def self.qualifier?(s)
115
+ s and (s =~ /[J|S]r[.]?/ or s =~ /\AI+\Z/)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,184 @@
1
+ require 'set'
2
+ require 'date'
3
+ require 'pp'
4
+ require 'stringio'
5
+ require 'caruby/util/options'
6
+ require 'caruby/util/collection'
7
+ require 'caruby/util/inflector'
8
+
9
+ class PrettyPrint
10
+ # Fixes the standard prettyprint gem SingleLine to add an output accessor and an optional output argument to {#initialize}.
11
+ class SingleLine
12
+ attr_reader :output
13
+
14
+ alias :base__initialize :initialize
15
+ private :base__initialize
16
+
17
+ # Allow output to be optional, defaulting to ''
18
+ def initialize(output='', maxwidth=nil, newline=nil)
19
+ base__initialize(output, maxwidth, newline)
20
+ end
21
+ end
22
+ end
23
+
24
+ # A PrintWrapper prints arguments by calling a printer proc.
25
+ class PrintWrapper < Proc
26
+ # Creates a new PrintWrapper on the given arguments.
27
+ def initialize(*args)
28
+ super()
29
+ @args = args
30
+ end
31
+
32
+ # Sets the arguments to wrap with this wrapper's print block and returns self.
33
+ def wrap(*args)
34
+ @args = args
35
+ self
36
+ end
37
+
38
+ # Calls this PrintWrapper's print procedure on its arguments.
39
+ def to_s
40
+ @args.empty? ? 'nil' : call(*@args)
41
+ end
42
+
43
+ alias :inspect :to_s
44
+ end
45
+
46
+ class Object
47
+ # Prints this object's class demodulized name and object id.
48
+ def print_class_and_id
49
+ "#{self.class.qp}@#{object_id}"
50
+ end
51
+
52
+ # qp, an abbreviation for quick-print, calls {#print_class_and_id} in this base implementation.
53
+ alias :qp :print_class_and_id
54
+
55
+ # Formats this object as a String with PrettyPrint.
56
+ # If the :single_line option is set, then the output is printed to a single line.
57
+ def pp_s(options=nil)
58
+ s = StringIO.new
59
+ if Options.get(:single_line, options) then
60
+ PP.singleline_pp(self, s)
61
+ else
62
+ PP.pp(self, s)
63
+ end
64
+ s.rewind
65
+ s.read.chomp
66
+ end
67
+ end
68
+
69
+ class Numeric
70
+ # qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
71
+ alias :qp :to_s
72
+ end
73
+
74
+ class String
75
+ # qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
76
+ alias :qp :to_s
77
+ end
78
+
79
+ class TrueClass
80
+ # qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
81
+ alias :qp :to_s
82
+ end
83
+
84
+ class FalseClass
85
+ # qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
86
+ alias :qp :to_s
87
+ end
88
+
89
+ class NilClass
90
+ # qp, an abbreviation for quick-print, is an alias for {#inspect} in this NilClass.
91
+ alias :qp :inspect
92
+ end
93
+
94
+ class Symbol
95
+ # qp, an abbreviation for quick-print, is an alias for {#inspect} in this Symbol class.
96
+ alias :qp :inspect
97
+ end
98
+
99
+ class Module
100
+ # qp, an abbreviation for quick-print, prints this module's name unqualified by a parent module prefix.
101
+ def qp
102
+ name[/\w+$/]
103
+ end
104
+ end
105
+
106
+ module Enumerable
107
+ # qp, short for quick-print, prints a collection Enumerable with a filter that calls qp on each item.
108
+ # Non-collection Enumerables delegate to the superclass method.
109
+ def qp
110
+ wrap { |item| item.qp }.pp_s
111
+ end
112
+
113
+ # If the transformer block is given to this method, then the transformer block to each
114
+ # enumerated item before pretty-printing the result.
115
+ def pp_s(options=nil, &transformer)
116
+ # delegate to Object if no block
117
+ return super(options) unless block_given?
118
+ # make a print wrapper
119
+ wrapper = PrintWrapper.new { |item| yield item }
120
+ # print using the wrapper on each item
121
+ wrap { |item| wrapper.wrap(item) }.pp_s(options)
122
+ end
123
+
124
+ # Pretty-prints the content within brackets, as is done by the Array pretty printer.
125
+ def pretty_print(q)
126
+ q.group(1, '[', ']') {
127
+ q.seplist(self) {|v|
128
+ q.pp v
129
+ }
130
+ }
131
+ end
132
+
133
+ # Pretty-prints the cycle within brackets, as is done by the Array pretty printer.
134
+ def pretty_print_cycle(q)
135
+ q.text(empty? ? '[]' : '[...]')
136
+ end
137
+ end
138
+
139
+ module Hashable
140
+ # qp, short for quick-print, prints this Hashable with a filter that calls qp on each key and value.
141
+ def qp
142
+ qph = {}
143
+ each { |k, v| qph[k.qp] = v.qp }
144
+ qph.pp_s
145
+ end
146
+
147
+ def pretty_print(q)
148
+ Hash === self ? q.pp_hash(self) : q.pp_hash(to_hash)
149
+ end
150
+
151
+ def pretty_print_cycle(q)
152
+ q.text(empty? ? '{}' : '{...}')
153
+ end
154
+ end
155
+
156
+ class String
157
+ # Pretty-prints this String using the Object pretty_print rather than Enumerable pretty_print.
158
+ def pretty_print(q)
159
+ q.text self
160
+ end
161
+ end
162
+
163
+ class DateTime
164
+ def pretty_print(q)
165
+ q.text(strftime)
166
+ end
167
+
168
+ # qp, an abbreviation for quick-print, is an alias for {#to_s} in this primitive class.
169
+ alias :qp :to_s
170
+ end
171
+
172
+ class Set
173
+ # Formats this set using {Enumerable#pretty_print}.
174
+ def pretty_print(q)
175
+ # mark this object as visited; this fragment is inferred from pp.rb and is necessary to detect a cycle
176
+ Thread.current[:__inspect_key__] << __id__
177
+ to_a.pretty_print(q)
178
+ end
179
+
180
+ # The pp.rb default pretty printing method for general objects that are detected as part of a cycle.
181
+ def pretty_print_cycle(q)
182
+ to_a.pretty_print_cycle(q)
183
+ end
184
+ end
@@ -0,0 +1,112 @@
1
+ require 'yaml'
2
+ require 'set'
3
+ require 'caruby/util/log'
4
+ require 'caruby/util/pretty_print'
5
+ require 'caruby/util/collection'
6
+ require 'caruby/util/merge'
7
+
8
+ module CaRuby
9
+ # Exception raised if a configuration property is missing or invalid.
10
+ class ConfigurationError < RuntimeError; end
11
+
12
+ # A Properties instance encapsulates a properties file accessor. The properties are stored
13
+ # in YAML format.
14
+ class Properties < Hash
15
+ # Creates a new Properties object. If the file argument is given, then the properties are loaded from
16
+ # that file.
17
+ #
18
+ # Supported options include the following:
19
+ # * :merge - the properties which are merged rather than replaced when loaded from the property files
20
+ # * :required - the properties which must be set when the property files are loaded
21
+ # * :array - the properties whose comma-separated String input value is converted to an array value
22
+ def initialize(file=nil, options=nil)
23
+ super()
24
+ @merge_properties = Options.get(:merge, options, []).to_set
25
+ @required_properties = Options.get(:required, options, []).to_set
26
+ @array_properties = Options.get(:array, options, []).to_set
27
+ load_properties(file) if file
28
+ end
29
+
30
+ # Returns a new Hash which associates this Properties' keys converted to symbols to the respective values.
31
+ def symbolize
32
+ Hash.new
33
+ end
34
+
35
+ # Returns whether the property key or its alternate is defined.
36
+ def has_property?(key)
37
+ has_key?(key) or has_key?(alternate_key(key))
38
+ end
39
+
40
+ # Returns the property value for the key. If there is no String key entry but there is a
41
+ # alternate key entry, then this method returns the alternate key value.
42
+ def [](key)
43
+ super(key) or super(alternate_key(key))
44
+ end
45
+
46
+ # Returns the property value for the key. If there is no key entry but there is an
47
+ # alternate key entry, then alternate key entry is set.
48
+ def []=(key, value)
49
+ return super if has_key?(key)
50
+ alt = alternate_key(key)
51
+ has_key?(alt) ? super(alt, value) : super
52
+ end
53
+
54
+ # Deletes the entry for the given property key or its alternate.
55
+ def delete(key)
56
+ key = alternate_key(key) unless has_key?(key)
57
+ super
58
+ end
59
+
60
+ # Loads the specified properties file, replacing any existing properties.
61
+ #
62
+ # If a key is included in this Properties merge_properties array, then the
63
+ # old value for that key will be merged with the new value for that key
64
+ # rather than replaced.
65
+ #
66
+ # This method reloads a property file that has already been loaded.
67
+ #
68
+ # Raises ConfigurationError if file doesn't exist or couldn't be parsed.
69
+ def load_properties(file)
70
+ raise ConfigurationError.new("Properties file not found: #{File.expand_path(file)}") unless File.exists?(file)
71
+ logger.debug { "Loading properties file #{file}..." }
72
+ properties = {}
73
+ begin
74
+ YAML::load_file(file).each { |key, value| properties[key.to_sym] = value }
75
+ rescue
76
+ raise ConfigurationError.new("Could not read properties file #{file}: " + $!)
77
+ end
78
+ # Uncomment the following line to print detail properties.
79
+ #logger.debug { "#{file} properties:\n#{properties.pp_s}" }
80
+ # parse comma-delimited string values of array properties into arrays
81
+ @array_properties.each do |key|
82
+ value = properties[key]
83
+ if String === value then
84
+ properties[key] = value.split(/,\s*/)
85
+ end
86
+ end
87
+ # if the key is a merge property key, then perform a deep merge.
88
+ # otherwise, do a shallow merge of the property value into this property hash.
89
+ deep, shallow = properties.partition { |key, value| @merge_properties.include?(key) }
90
+ merge!(deep, :deep)
91
+ merge!(shallow)
92
+ end
93
+
94
+ private
95
+
96
+ # Returns key as a Symbol if key is a String, key as a String if key is a Symbol,
97
+ # or nil if key is neither a String nor a Symbol.
98
+ def alternate_key(key)
99
+ case key
100
+ when String then key.to_sym
101
+ when Symbol then key.to_s
102
+ end
103
+ end
104
+
105
+ # Validates that the required properties exist.
106
+ def validate_properties
107
+ @required_properties.each do |key|
108
+ raise CaRuby::ConfigurationError.new("A required #{@application} property was not found: #{key}") unless has_property?(key)
109
+ end
110
+ end
111
+ end
112
+ end