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.
-
#configure_importer ⇒ Object
private
Initializes this importer on demand.
-
#const_missing(sym) ⇒ Class
Imports a Java class constant on demand.
- #definitions(*directories) ⇒ Object private
-
#introspect(klass) ⇒ Object
private
Introspects the given class.
- #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
Sets 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.
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 |
# File 'lib/jinx/importer.rb', line 193 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. if Class === klass then sc = klass.superclass unless @introspected.include?(sc) or sc == Java::java.lang.Object then (sc) end end # Include this resource module into the class. unless klass < self then m = self klass.class_eval { include m } 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) # Introspect the Java properties. introspect(klass) klass.add_attribute_value_initializer if Class === klass # Set the class domain module. klass.domain_module = self # Add referenced domain class metadata as necessary. mod = klass.parent_module klass.each_property do |prop| ref = prop.type if ref.nil? then raise MetadataError.new("#{self} #{prop} domain type is unknown.") end unless @introspected.include?(ref) or ref.parent_module != mod then logger.debug { "Introspecting #{qp} #{prop} reference #{ref.qp}..." } (ref) end end logger.debug("#{self}::#{klass.qp} metadata added.") end |
#configure_importer ⇒ Object (private)
Initializes this importer on demand. This method is called the first time a class is referenced.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/jinx/importer.rb', line 93 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 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.
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 |
# File 'lib/jinx/importer.rb', line 34 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.nil? then # 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.include?(klass) then (klass) # Print the class meta-data. logger.info(klass.pp_s) end klass end |
#definitions(*directories) ⇒ Object (private)
121 122 123 |
# File 'lib/jinx/importer.rb', line 121 def definitions(*directories) @definitions = directories end |
#introspect(klass) ⇒ Object (private)
Introspects the given class.
237 238 239 |
# File 'lib/jinx/importer.rb', line 237 def introspect(klass) klass.introspect end |
#load_definitions ⇒ Object (private)
125 126 127 128 129 130 131 132 |
# File 'lib/jinx/importer.rb', line 125 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.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/jinx/importer.rb', line 137 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 if necessary. klasses.each { |klass| (klass) unless @introspected.include?(klass) } # Load the classes. klasses.each do |klass| file = srcs[klass] logger.debug { "Loading #{klass.qp} definition #{file}..." } require file logger.debug { "Loaded #{klass.qp} definition #{file}." } end logger.debug { "Loaded the class definitions in #{dir}." } end |
#module_for_name(name) ⇒ Module
Returns the corresponding module.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/jinx/importer.rb', line 73 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
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.
113 114 115 |
# File 'lib/jinx/importer.rb', line 113 def packages(*names) @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.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/jinx/importer.rb', line 172 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.
157 158 159 160 161 162 163 164 165 166 |
# File 'lib/jinx/importer.rb', line 157 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 resolve_class(name) end.invert end |