Class: Jenkins::Plugin::Proxies

Inherits:
Object
  • Object
show all
Defined in:
lib/jenkins/plugin/proxies.rb

Overview

Maps JRuby objects part of the idomatic Ruby API to a plain Java object representation and vice-versa.

One of the pillars of Jenkins Ruby plugins is that writing plugins must “feel” like native Ruby, and not merely like scripting Java with Ruby. To this end, jenkins-plugins provides a idiomatic Ruby API that sits on top of the native Jenkins Java API with which plugin developers can interact.

This has two consequences. Native Ruby objects authored as part of the plugin must have a foreign (Java) representation with which they can wander about the Jenkins universe and possibly interact with other foreign objects and APIs. Also, Foreign objects coming in from Jenkins at large should be wrapped, where possible to present an idomatic interface..

Finally, Native plugin that had been wrapped and are comping home must be unwrapped from their external form.

For all cases, we want to maintain referential integrety so that the same object always uses the same external form, etc… so there is one instance of the ‘Proxies` class per plugin which will reuse mappings where possible.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plugin) ⇒ Proxies

Returns a new instance of Proxies.



35
36
37
38
39
# File 'lib/jenkins/plugin/proxies.rb', line 35

def initialize(plugin)
  @plugin = plugin
  @int2ext = java.util.Collections.synchronizedMap(java.util.WeakHashMap.new)
  @ext2int = java.util.Collections.synchronizedMap(java.util.WeakHashMap.new)
end

Class Method Details

.clearObject

Remove all class linkages. This is mainly for testing purposes.



165
166
167
168
# File 'lib/jenkins/plugin/proxies.rb', line 165

def self.clear
  @@intcls2extcls = {}
  @@extcls2intcls = {}
end

.register(internal_class, external_class) ⇒ Object

Associated the the Ruby class ‘internal_class` with the Java class `external_class`.

Whenever a plugin is importing or exporting an object to the other side, it will first see if there is an instance already linked. If not, it will try to create the other side of the link by constructing it via reflection. ‘register` links two classes together so that links can be built automatically.

Parameters:

  • internal_class (Class)

    the Ruby class

  • external_class (java.lang.Class)

    the Java class on the other side of this link.



158
159
160
161
# File 'lib/jenkins/plugin/proxies.rb', line 158

def self.register(internal_class, external_class)
  @@intcls2extcls[internal_class] = external_class
  @@extcls2intcls[external_class] = internal_class
end

Instance Method Details

#deref(reflist, object) ⇒ Object



142
143
144
145
146
# File 'lib/jenkins/plugin/proxies.rb', line 142

def deref(reflist, object)
  if ref = reflist[object]
    ref.is_a?(java.lang.ref.Reference) ? ref.get() : ref
  end
end

#export(object) ⇒ java.lang.Object

Reflect a native Ruby object into its External Java form.

Try to find a suitable form for this object and if one is found then decorate it. If the object already is a java.lang.Object, then just let it pass through.

Parameters:

  • object (Object)

    the ruby object that is being exported to Java

Returns:

  • (java.lang.Object)

    the Java wrapper that provides an interface to ‘object`

Raises:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/jenkins/plugin/proxies.rb', line 77

def export(object)
  if proxy = deref(@int2ext, object)
    return proxy
  end

  cls = object.class
  if cls.respond_to? :java_class
    return object
  end
  while cls do
    if external_class = @@intcls2extcls[cls]
      external = external_class.new(@plugin, object)
      linkout(object, external)
      return external
    end
    cls = cls.superclass
  end
  raise ExportError, "unable to find suitable Java Proxy for #{object.inspect}"
end

#import(object) ⇒ Object

Reflect a foreign Java object into the context of this plugin.

If the object is a native plugin object that had been previously exported, then it will unwrapped.

Otherwise, we try to choose the best idiomatic API object for this foreign object

Parameters:

  • object (Object)

    the object to bring in from the outside

Returns:

  • the best representation of that object for this plugin



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/jenkins/plugin/proxies.rb', line 51

def import(object)
  if proxy = deref(@ext2int, object)
    return proxy
  end
  cls = object.class
  while cls do
    if internal_class = @@extcls2intcls[cls]
      internal = internal_class.new(object)
      linkin(internal, object)
      return internal
    end
    cls = cls.superclass
  end
  internal = OpaqueJavaObject.new(object)
  linkin(internal, object)
  return internal
end

#linkin(internal, external) ⇒ Object

Forms an incoming reference to an internal Ruby object from an external Java object. These two objects will be referentially equivalent whenever they are passed back and forth between the Ruby plugin and the Jenkins runtime.

The fact that this reference is “in” means that GC-wise, the internal Ruby object depends on the external Java object. In other words, it will not be garbage collected until the Java object is.

This behavior is important for things like AbstractBuild objects. We don’t want our Ruby ‘Build` object that corresponds to it to be GC’d until the ‘hudson.model.AbstractBuild` it represents is itself collected. That allows us to maintain state on the Ruby object, and know that the same state will be accessible wherever.

Only weak refereces are maintained to the external Java object.

Parameters:

  • internal (Object)

    the object on the Ruby side of the link

  • external (java.lang.Object)

    the object on the Java side of the link



118
119
120
121
# File 'lib/jenkins/plugin/proxies.rb', line 118

def linkin(internal, external)
  @int2ext.put(internal, java.lang.ref.WeakReference.new(external))
  @ext2int.put(external, internal)
end

#linkout(internal, external) ⇒ Object

Forms an outgoing reference from an internal Ruby object to an external Java object. These two objects will be referentially equivalent whenever they are passed back and forth between the Ruby plugin and the Jenkins runtime.

The fact that this reference goes “out” means that GC-wise, the external Java object depends on the internal Ruby object. In other words, The Java object will not be garbage collected until the local Ruby object is.

Only weak refereces are maintained to the internal Ruby object.

Parameters:

  • internal (Object)

    the object on the Ruby side of the link

  • external (java.lang.Object)

    the object on the Java side of the link



137
138
139
140
# File 'lib/jenkins/plugin/proxies.rb', line 137

def linkout(internal, external)
  @int2ext.put(internal, external)
  @ext2int.put(external, java.lang.ref.WeakReference.new(internal))
end