caruby-core 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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