Module: Jinx::Importer
- Defined in:
- lib/jinx/importer.rb
Overview
Importer extends a module with Java class import support. Importer is an aspect of a Metadata module. Including Metadata
in an application domain module extends that module with Importer capability.
The Importer module imports a Java class or interface on demand by referencing the class name in the context of the module. The imported class Metadata is introspected.
Import on demand is induced by a reference to the class. The family
example illustrates a domain package extended with metadata capability. The first non-definition reference to Family::Parent
imports the Java class family.Parent
into the JRuby class wrapper Family
and introspects the Java property meta-data.
Instance Method Summary collapse
-
#add_metadata(klass) ⇒ Object
private
Introspects the given class meta-data.
- #add_superclass_metadata(klass) ⇒ Object private
-
#configure_importer ⇒ Object
private
Initializes this importer on demand.
-
#const_missing(sym) ⇒ Class
Imports a Java class constant on demand.
-
#contains?(klass) ⇒ Boolean
Returns whether the given Java class or interface is imported into this domain module.
-
#definitions(*directories) ⇒ <String>
private
If the given directories argument is empty, then return the definition directories.
-
#introspected?(klass) ⇒ Boolean
private
Whether the class is an introspected Resource class.
-
#introspectible?(klass) ⇒ Boolean
private
Whether the given class has a Java package among this module’s #packages and has not yet been introspected.
- #load_definition(klass, file) ⇒ Object private
- #load_definitions ⇒ Object private
-
#load_dir(dir) ⇒ Object
private
Loads the Ruby source files in the given directory.
-
#module_for_name(name) ⇒ Module
The corresponding module.
-
#packages(*names) ⇒ Object
(also: #package)
private
If the given names argument is empty, then returns the package names.
-
#resolve_class(name, package = nil) ⇒ Class?
private
The Resource class imported into this module, or nil if the class cannot be resolved.
-
#shims(*classes) ⇒ Object
Declares that the given Resource classes will be dynamically modified.
-
#sources(dir) ⇒ {Class => String}
private
The source class => file hash.
Instance Method Details
#add_metadata(klass) ⇒ Object (private)
Introspects the given class meta-data.
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 |
# File 'lib/jinx/importer.rb', line 230 def (klass) logger.debug("Adding #{self}::#{klass.qp} metadata...") # Mark the class as introspected. Do this first to preclude a recursive loop back # into this method when the references are introspected below. @introspected << klass # Add the superclass meta-data if necessary. (klass) # Include this resource module into the class, unless this has already occurred. unless klass < self then m = self klass.class_eval { include m } end # Import the class into this resource module, unless this has already occurred. name = klass.name.demodulize unless const_defined?(name) then java_import(klass.java_class.name) end # Add introspection capability to the class. md_mod = @metadata_module || Metadata logger.debug { "Extending #{self}::#{klass.qp} with #{md_mod.name}..." } klass.extend(md_mod) # Set the class domain module. klass.domain_module = self # Introspect the Java properties. klass.introspect # Add the {attribute => value} initializer. klass.add_attribute_value_initializer if Class === klass # Add referenced domain class metadata as necessary. klass.each_property do |prop| ref = prop.type if ref.nil? then raise MetadataError.new("#{self} #{prop} domain type is unknown.") end if introspectible?(ref) then logger.debug { "Introspecting the #{klass.qp} #{prop} reference type #{ref.qp}..." } (ref) end end # If the class has a definition file but does not resolve to a standard package, then # load it now based on the demodulized class name match. file = @unresolved_defs[name] load_definition(klass, file) if file logger.debug("#{self}::#{klass.qp} metadata added.") end |
#add_superclass_metadata(klass) ⇒ Object (private)
274 275 276 277 278 279 |
# File 'lib/jinx/importer.rb', line 274 def (klass) if Class === klass then sc = klass.superclass (sc) unless introspected?(sc) or sc == Java::java.lang.Object end end |
#configure_importer ⇒ Object (private)
Initializes this importer on demand. This method is called the first time a class is referenced.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/jinx/importer.rb', line 109 def configure_importer # The default package conforms to the JRuby convention for mapping a package name # to a module name. @packages ||= [name.split('::').map { |n| n.downcase }.join('.')] @packages.each do |pkg| begin eval "java_package Java::#{pkg}" rescue Exception => e raise ArgumentError.new("#{self} Java package #{pkg} not found - #{$!}") end end # The introspected classes. @introspected = Set.new # The name => file hash for file definitions that are not in the packages. @unresolved_defs = {} end |
#const_missing(sym) ⇒ Class
Imports a Java class constant on demand. If the class does not already include this module’s mixin, then the mixin is included in the class.
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 |
# File 'lib/jinx/importer.rb', line 48 def const_missing(sym) # Load the class definitions in the source directory, if necessary. # If a load is performed as a result of referencing the given symbol, # then dereference the class constant again after the load, since the class # might have been loaded or referenced during the load. unless defined? @introspected then configure_importer load_definitions return const_get(sym) end # Append the symbol to the package to make the Java class name. logger.debug { "Detecting whether #{sym} is a #{self} Java class..." } klass = @packages.detect_value do |pkg| begin java_import "#{pkg}.#{sym}" rescue NameError nil end end if klass then logger.debug { "Added #{klass} to the #{self} module." } else # Not a Java class; print a log message and pass along the error. logger.debug { "#{sym} is not recognized as a #{self} Java class." } super end # Introspect the Java class meta-data, if necessary. unless introspected?(klass) then (klass) # Print the class meta-data. logger.info(klass.pp_s) end klass end |
#contains?(klass) ⇒ Boolean
Returns whether the given Java class or interface is imported into this domain module. This method imports the class or interface on demand.
34 35 36 37 38 39 40 |
# File 'lib/jinx/importer.rb', line 34 def contains?(klass) # Import a domain class on demand by referencing the class base name in the context # of this module. If the class name can be resolved, then also check that it # was introspected. Primitive classes like String can be resolved, but are not # introspected. Domain classes are introspected when the name is resolved. (!!const_get(klass.name.demodulize) rescue false) and @introspected.include?(klass) end |
#definitions(*directories) ⇒ <String> (private)
If the given directories argument is empty, then return the definition directories. Otherwise, set the definitions.
144 145 146 |
# File 'lib/jinx/importer.rb', line 144 def definitions(*directories) directories.empty? ? @definitions : @definitions = directories end |
#introspected?(klass) ⇒ Boolean (private)
Returns whether the class is an introspected Resource class.
283 284 285 |
# File 'lib/jinx/importer.rb', line 283 def introspected?(klass) klass < Resource and klass.introspected? end |
#introspectible?(klass) ⇒ Boolean (private)
Returns whether the given class has a Java package among this module’s #packages and has not yet been introspected.
290 291 292 |
# File 'lib/jinx/importer.rb', line 290 def introspectible?(klass) not introspected?(klass) and Class === klass and @packages.include?(klass.java_class.package.name) end |
#load_definition(klass, file) ⇒ Object (private)
176 177 178 179 180 181 182 183 184 185 |
# File 'lib/jinx/importer.rb', line 176 def load_definition(klass, file) logger.debug { "Loading the #{klass.qp} definition #{file}..." } begin require file rescue Exception logger.error("Could not load the #{klass} definition #{file} - " + $!) raise end logger.debug { "Loaded the #{klass.qp} definition #{file}." } end |
#load_definitions ⇒ Object (private)
148 149 150 151 152 153 154 155 |
# File 'lib/jinx/importer.rb', line 148 def load_definitions return if @definitions.nil_or_empty? # Load the class definitions in the source directories. @definitions.each { |dir| load_dir(File.(dir)) } # Print each introspected class's content. @introspected.sort { |k1, k2| k1.name <=> k2.name }.each { |klass| logger.info(klass.pp_s) } true end |
#load_dir(dir) ⇒ Object (private)
Loads the Ruby source files in the given directory.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/jinx/importer.rb', line 160 def load_dir(dir) logger.debug { "Loading the class definitions in #{dir}..." } # Import the classes. srcs = sources(dir) # Introspect and load the classes in reverse class order, i.e. superclass before subclass. klasses = srcs.keys.transitive_closure { |k| [k.superclass] }.select { |k| srcs[k] }.reverse # Introspect the classes as necessary. klasses.each { |klass| (klass) unless introspected?(klass) } # Load the classes. klasses.each do |klass| file = srcs[klass] load_definition(klass, file) end logger.debug { "Loaded the class definitions in #{dir}." } end |
#module_for_name(name) ⇒ Module
Returns the corresponding module.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/jinx/importer.rb', line 89 def module_for_name(name) begin # Incrementally resolve the module. name.split('::').inject(self) { |ctxt, mod| ctxt.const_get(mod) } rescue NameError # If the application domain module set the parent module i.v.. then continue # the look-up in that parent importer. raise unless @parent_importer mod = @parent_importer.module_for_name(name) if mod then logger.debug { "Module #{name} found in #{qp} parent module #{@parent_importer}." } end mod end end |
#packages(*names) ⇒ Object (private) Also known as: package
If the given names argument is empty, then returns the package names. Otherwise, sets the package names. The default package conforms to the JRuby convention for mapping a package name to a module name, e.g. the MyApp::Domain
default package is myapp.domain
. Clients set the package if it differs from the default.
132 133 134 |
# File 'lib/jinx/importer.rb', line 132 def packages(*names) names.empty? ? @packages : @packages = names end |
#resolve_class(name, package = nil) ⇒ Class? (private)
Returns the Resource class imported into this module, or nil if the class cannot be resolved.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/jinx/importer.rb', line 209 def resolve_class(name, package=nil) if const_defined?(name) then return const_get(name) end if package.nil? then return @packages.detect_value { |pkg| resolve_class(name, pkg) } end # Append the class name to the package to make the Java class name. full_name = "#{package}.#{name}" # If the class is already imported, then java_import returns nil. In that case, # evaluate the Java class. begin java_import(full_name) rescue module_eval("Java::#{full_name}") rescue nil end end |
#shims(*classes) ⇒ Object
Declares that the given Resource classes will be dynamically modified. This method introspects the classes, if necessary.
23 24 25 26 |
# File 'lib/jinx/importer.rb', line 23 def shims(*classes) # Nothing to do, since all this method does is ensure that the arguments are # introspected when they are referenced. end |
#sources(dir) ⇒ {Class => String} (private)
Returns the source class => file hash.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/jinx/importer.rb', line 189 def sources(dir) # the domain class definitions files = Dir.glob(File.join(dir, "*.rb")) # Infer each class symbol from the file base name. # Ignore files which do not resolve to a class. files.to_compact_hash do |file| name = File.basename(file, ".rb").camelize klass = resolve_class(name) if klass.nil? then logger.debug { "The class definition file #{file} does not correspond to a class in the standard #{qp} packages." } @unresolved_defs[name] = file end klass end.invert end |