I'm running a test where I take 1,000 Strings that represent an ID. I create an ExecutorService with 100 threads, loop over the 1,000 Strings, and create a Callable for each. This works fine in Java, but I found in Groovy that the anonymous inner class Callable is not storing the iterated value.
Here is my test:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class CallableTest {
public static void main( String[] args ) throws InterruptedException, ExecutionException {
CallableTest test = new CallableTest();
test.runTest();
}
public List<String> setupTest(){
List<String> ids = new ArrayList<String>();
for(int i = 0; i < 1000; i++) {
ids.add( "ID_" + i );
}
return ids;
}
public void runTest() throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(100);
List<Future<String>> futures = new ArrayList<Future<String>>();
List<String> ids = setupTest();
for(final String id : ids) {
Future<String> future = executor.submit(new Callable<String>() {
#Override
public String call() throws Exception {
return doSomethingWithId(id);
}
});
futures.add( future );
}
for(Future<String> future : futures) {
String message = future.get();
System.out.println(message);
}
}
public String doSomethingWithId(String id) {
return "Doing something with ID: " + id;
}
}
The problem in Groovy is here:
for(final String id : ids) {
Future<String> future = executor.submit(new Callable<String>() {
#Override
public String call() throws Exception {
// The ID value here is not the ID value from when the Callable was created
return doSomethingWithId(id);
}
});
futures.add( future );
}
As you can see in my comment, the id value when calling the method doSomethingWithId is not the same value as it was when the Callable was created. This leads to Doing something with ID: ID_999 being printed majority of the time.
If I copy this over to a Java project and run it, it runs as expected. I get Doing something with ID: ID_x from 0-999 with no duplicates.
Why doesn't this work in Groovy? My understanding is that Groovy should support anonymous inner classes, but it appears that the anonymous inner class Callable is not capturing the outer variable id state when it creates the anonymous class. I'm using Java 7 with Groovy 2.3.10.
UPDATE
I found that adding the following made this work:
for(final String id : ids) {
final String myId = id // This makes it work
Future<String> future = executor.submit(new Callable<String>() {
#Override
public String call() throws Exception {
return doSomethingWithId(myId);
}
});
futures.add( future );
}
Seems like Groovy isn't actually making the value returned from the iterator final?
Related
I have the following code. I have an abstract JobParams, a class extending that abstract GradleJobParams, and a gjp variable with value using anonymous class declaration.
I want to test the overriding behavior of groovy. I can override the method setupRoot() but not the property testVar, why is that?
Tested on: https://groovyconsole.appspot.com/script/5146436232544256
abstract class JobParams {
int root
def testVar=1
def setupRoot () {
println("The root");
}
def printTestVar () {
println("The testVar:" + testVar);
}
}
class GradleJobParams extends JobParams {
}
def gjp = [
testVar:3,
setupRoot:{
println("Override root");
}
] as GradleJobParams;
println("Starting");
gjp.printTestVar();
gjp.setupRoot();
The result is:
Starting
The testVar:1
Override root
Java (and thus Groovy) does not support overriding fields from the parent class with subclassing. Instead, it uses a mechanism called hiding fields:
Hiding Fields
Within a class, a field that has the same name as a field in the superclass hides the superclass's field, even if their types are different. Within the subclass, the field in the superclass cannot be referenced by its simple name. Instead, the field must be accessed through super, which is covered in the next section. Generally speaking, we don't recommend hiding fields as it makes code difficult to read.
Source: https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html
It can be simply illustrated with the following example in Java:
final class SubclassHiddingFieldExample {
static abstract class A {
int value = 10;
void printValue1() {
System.out.println(value);
}
void printValue2() {
System.out.println(this.value);
}
void printValue3() {
System.out.println(((B)this).value);
}
}
static class B extends A {
int value = 12;
}
public static void main(String[] args) {
final B b = new B();
b.printValue1();
b.printValue2();
b.printValue3();
}
}
Output:
10
10
12
As you can see, only printValue3 prints out 3, because it cast this explicitly to B class.
Now, if you look at the decompiled bytecode of your JobParams class, you can see that the printTestVar method code is an equivalent of the following Java code:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;
public abstract class JobParams implements GroovyObject {
private int root;
private Object testVar;
public JobParams() {
CallSite[] var1 = $getCallSiteArray();
byte var2 = 1;
this.testVar = Integer.valueOf(var2);
MetaClass var3 = this.$getStaticMetaClass();
this.metaClass = var3;
}
public Object setupRoot() {
CallSite[] var1 = $getCallSiteArray();
return var1[0].callCurrent(this, "The root");
}
public Object printTestVar() {
CallSite[] var1 = $getCallSiteArray();
return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
}
public MetaClass getMetaClass() {
MetaClass var10000 = this.metaClass;
if (var10000 != null) {
return var10000;
} else {
this.metaClass = this.$getStaticMetaClass();
return this.metaClass;
}
}
public void setMetaClass(MetaClass var1) {
this.metaClass = var1;
}
public Object invokeMethod(String var1, Object var2) {
return this.getMetaClass().invokeMethod(this, var1, var2);
}
public Object getProperty(String var1) {
return this.getMetaClass().getProperty(this, var1);
}
public void setProperty(String var1, Object var2) {
this.getMetaClass().setProperty(this, var1, var2);
}
public int getRoot() {
return this.root;
}
public void setRoot(int var1) {
this.root = var1;
}
public Object getTestVar() {
return this.testVar;
}
public void setTestVar(Object var1) {
this.testVar = var1;
}
}
You can see that the line that prints out the value of the testVar field is represented by:
return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
It means that no matter what value of testVar your subclass defines, the printTestVar method uses testVar field defined in the JobParams class. Period.
Using Groovy auto getter methods
There is one way you to implement the expected behavior. Every class field in Groovy has a getter method associated with that field compiled by Groovy for you. It means that you can access testVar by calling the getTestVar() method generated by the Groovy compiler. You can use it to override the value returned by a getter method for any field from the subclass. Consider the following example:
abstract class JobParams {
int root
def testVar=1
def setupRoot () {
println("The root");
}
def printTestVar () {
println("The testVar:" + getTestVar()); // <-- using a getTestVar() method instead a testVar field
}
}
class GradleJobParams extends JobParams {
}
def gjp = [
getTestVar: 3, // <-- stubbing getTestVar() method to return a different value
setupRoot:{
println("Override root");
}
] as GradleJobParams;
println("Starting");
gjp.printTestVar();
gjp.setupRoot();
Output:
Starting
The testVar:3
Override root
I am trying to use Groovy in order to convert string to the reflection code but I have "No such property" exception.
I have tried to make global all variables, change the reflection code and put #Field notation but problem still remaining. I put Groovy code inside "runTestSamples()".
MainClass - Test2
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.jacoco.agent.AgentJar;
import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IClassCoverage;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.SessionInfoStore;
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.LoggerRuntime;
import org.jacoco.core.runtime.RuntimeData;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
public class Test2 {
private Runnable targetInstance;
public Class<?> targetClass;
private static HashMap<Integer, String> testSamples;
private static HashMap<String, Integer> coverageData;
public String targetName;
public IRuntime runtime;
public Instrumenter instr;
public InputStream original;
public byte[] instrumented;
public RuntimeData data;
public MemoryClassLoader memoryClassLoader;
static Test2 t2 = new Test2();
int a;
public static void main(String[] args) throws Exception {
testSamples = new HashMap<Integer, String>();
coverageData = new HashMap<String, Integer>();
try {
t2.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
public void execute() throws Exception {
testSamples = new HashMap<Integer, String>();
coverageData = new HashMap<String, Integer>();
targetName = SUTClass.class.getName();
runtime = new LoggerRuntime();
instr = new Instrumenter(runtime);
original = getTargetClass(targetName);
instrumented = instr.instrument(original, targetName);
original.close();
data = new RuntimeData();
runtime.startup(data);
memoryClassLoader = new MemoryClassLoader();
memoryClassLoader.addDefinition(targetName, instrumented);
targetClass = (Class<? extends Runnable>) memoryClassLoader.loadClass(targetName);
targetInstance = (Runnable) targetClass.newInstance();
// Test samples
runTestSamples();
targetInstance.run();
final ExecutionDataStore executionData = new ExecutionDataStore();
final SessionInfoStore sessionInfos = new SessionInfoStore();
data.collect(executionData, sessionInfos, false);
runtime.shutdown();
final CoverageBuilder coverageBuilder = new CoverageBuilder();
final Analyzer analyzer = new Analyzer(executionData, coverageBuilder);
original = getTargetClass(targetName);
analyzer.analyzeClass(original, targetName);
original.close();
for (final IClassCoverage cc : coverageBuilder.getClasses()) {
coverageData.put("coveredInstructions", cc.getInstructionCounter().getCoveredCount());
}
System.out.println(coverageData.get("coveredInstructions"));
System.out.println(a);
}
public static class MemoryClassLoader extends ClassLoader {
private final Map<String, byte[]> definitions = new HashMap<String, byte[]>();
public void addDefinition(final String name, final byte[] bytes) {
definitions.put(name, bytes);
}
#Override
protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
final byte[] bytes = definitions.get(name);
if (bytes != null) {
return defineClass(name, bytes, 0, bytes.length);
}
return super.loadClass(name, resolve);
}
}
private InputStream getTargetClass(final String name) {
final String resource = '/' + name.replace('.', '/') + ".class";
return getClass().getResourceAsStream(resource);
}
public void runTestSamples() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException, SecurityException, ClassNotFoundException {
// Test case
targetClass.getMethod("f", int.class, int.class).invoke(targetInstance, 2, 9);
// Groovy String to code
Binding binding = new Binding();
GroovyShell shell = new GroovyShell(binding);
Object value = shell.evaluate("targetClass.getMethod(\"f\", int.class, int.class).invoke(targetInstance, 2, 9);");
}
}
Exception
groovy.lang.MissingPropertyException: No such property: targetClass for class: Script1
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:65)
at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:51)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:309)
at Script1.run(Script1.groovy:1)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:437)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:475)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:446)
at Test2.runTestSamples(Test2.java:119)
at Test2.execute(Test2.java:66)
at Test2.main(Test2.java:43)
the problem in this code:
Binding binding = new Binding();
GroovyShell shell = new GroovyShell(binding);
Object value = shell.evaluate("targetClass.getMethod(\"f\", int.class, int.class).invoke(targetInstance, 2, 9);");
when you call shell.evaluate imagine that you call absolutely new class that doesnot know anything about your current variables like targetClass
so, GroovyShell telling that there is no such property: targetClass
to fix it - you have just to populate binding - pass the variables values and names that should be visible inside the shell.evaluate(...).
Binding binding = new Binding();
binding.setVariable("target", targetClass) //pass targetClass as target variable name
binding.setVariable("instance", targetInstance)
GroovyShell shell = new GroovyShell(binding);
Object value = shell.evaluate("target.getMethod(\"f\", int.class, int.class).invoke(instance, 2, 9)");
another point - groovy is already dynamic language and you could simplify your nested script from this:
target.getMethod("f", int.class, int.class).invoke(instance, 2, 9)
to this:
instance."f"(2, 9)
and finally maybe you don't need to use the groovyshell because the following code dynamycally calls the method:
class A{
def f(int a, int b){ a+b }
}
def instance = new A()
def method = "f"
def params = [2,9]
println instance."${method}"(params)
I am using XmlSlurper to parse an XML file for processing.
I need to find the line number, of some matching criteria, within the file.
Here is what I have so far:
def void findServlets() {
this.servlets = webFile.depthFirst().findAll{
it.name() == 'servlet'
}
println this.servlets
}
Now I need to find the correlating line number for every matching 'servlet'.
Using a SaxParser, I would do the following:
node.getUserData(SaxParser.KEY_LINE_NO)
How would I accomplish the same task on a GpathResult in groovy ?
Many thanks,
Michael
You cant do it directly, but you could decorate each element of the the parsed DOM with a line number attribute.
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.Attributes2Impl;
import javax.xml.parsers.ParserConfigurationException;
class MySlurper extends XmlSlurper {
public static final String LINE_NUM_ATTR = "_srmLineNum"
Locator locator
public MySlurper() throws ParserConfigurationException, SAXException {
super();
}
#Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
#Override
public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
Attributes2Impl newAttrs = new Attributes2Impl(attrs);
newAttrs.addAttribute(uri, LINE_NUM_ATTR, LINE_NUM_ATTR, "ENTITY", "" + locator.getLineNumber());
super.startElement(uri, localName, qName, newAttrs);
}
}
def text = '''
<root>
<a>one!</a>
<a>two!</a>
</root>'''
def root = new MySlurper().parseText(text)
root.a.each { println it.#_srmLineNum }
Here is a slight modification of #preetham's suggestion. This allows you to set both the start and end line numbers in the XML
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.Attributes2Impl;
import javax.xml.parsers.ParserConfigurationException;
class NumberedXmlParser extends XmlSlurper {
public static final String START_LINE = "_start"
public static final String END_LINE = "_end"
Locator locator
// stack and parent are normally private variables in XmlParser
// We create local references to these so that we can hook into them
List<Node> localStack = new ArrayList<Node>();
Node localParent;
public NumberedXmlParser() throws ParserConfigurationException, SAXException {
super();
}
#Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
#Override
public void startDocument() throws SAXException {
localParent = null;
super.startDocument();
}
#Override
public void endDocument() throws SAXException {
localStack.clear();
super.endDocument();
}
#Override
public Node createNode(Node parent, Object name, Map attributes) {
// Hack into this method to get access to the parent Node and stack
this.localParent = new Node(parent, name, attributes);
this.localParent["#$START_LINE"] = locator.getLineNumber();
this.localStack.add(this.localParent);
return this.localParent;
}
#Override
public void endElement(final String uri, final String localName, final String qName) {
if (!localStack.isEmpty()) {
Node element = localStack.remove(localStack.size() - 1);
element["#$END_LINE"] = locator.getLineNumber();
if (!localStack.isEmpty()) {
localParent = localStack.get(localStack.size() - 1);
}
}
super.endElement(uri, localName, qName);
}
}
This is a followup to my previous post at ToolTip Performance in XPages I have got the code to do it written (not tested) so I can't seem to get my Managed Bean to get called properly. My config contians the following:
<managed-bean id="ToolTip">
<managed-bean-name>WFSToolTip</managed-bean-name>
<managed-bean-class>ca.workflo.wfsToolTip.ToolTipText</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
and I have stripped my code down to the bare minimum:
package ca.workflo.wfsToolTip;
public class ToolTipText {
public String getToolTipText(String key){
return key;
}
}
My class is in the build path. I have a simple XPage with one filed on it and a tool tip for that field. The code for the tooltip is:
<xe:tooltip id="tooltip1" for="inputText1">
<xe:this.label>
<![CDATA[#{javascript:WFSToolTip.getToolTipText("More Stuff");}]]>
</xe:this.label>
</xe:tooltip>
When I load the test XPage in the browser I get an error that:
Error while executing JavaScript computed expression
Script interpreter error, line=1, col=12: Error calling method 'getToolTipText(string)' on java class 'ca.workflo.wfsToolTip.ToolTipText'
JavaScript code
1: WFSToolTip.getToolTipText("More Stuff");
I can't figure out why the call to getToolTipText would fail.
Can anyone see where I'm going wrong. This is my first Managed Bean and at the moment it is managing me rather than the other way around.
Thanks.
You need to:
- implement Serializable which boils down to state it and provide a version
- implement Map ... a little more work
Then you use Expression Language instead of SSJS. It would look like #{WFSToolTip["More Stuff"]}
This is how such a class would look like. You need to:
adjust the view name to reflect the name you want
the view needs to be flat, column 1 = tooltip name, column 2 = tooltip text
somewhere (on an admin/config page) you need to call WFSToolTip.clear(); (in SSJS) after you update the values in the configuration.
The example doesn't lazyload since running though a view navigator once is really fast. No point to do all these lookups.
Here you go:
package com.notessensei.xpages;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import lotus.domino.Base;
import lotus.domino.Database;
import lotus.domino.NotesException;
import lotus.domino.View;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import com.ibm.xsp.extlib.util.ExtLibUtil;
public class Parameters implements Serializable, Map<String, String> {
private final static String CONFIG_VIEW = "keywords";
private static final long serialVersionUID = 1L;
private final Map<String, String> internalMap = new HashMap<String, String>();
public Parameters() {
this.populateParameters(internalMap);
}
private void populateParameters(Map<String, String> theMap) {
Database d = ExtLibUtil.getCurrentDatabase();
try {
View v = d.getView(CONFIG_VIEW);
ViewEntryCollection vec = v.getAllEntries();
ViewEntry ve = vec.getFirstEntry();
ViewEntry nextVe = null;
while (ve != null) {
nextVe = vec.getNextEntry(ve);
// Load the parameters, column 0 is the key, column 0 the value
Vector colVal = ve.getColumnValues();
theMap.put(colVal.get(0).toString(), colVal.get(1).toString());
// Cleanup
this.shred(ve);
ve = nextVe;
}
// recycle, but not the current database!!!
this.shred(ve, nextVe, vec, v);
} catch (NotesException e) {
e.printStackTrace();
}
}
public void clear() {
this.internalMap.clear();
this.populateParameters(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, String>> entrySet() {
return this.internalMap.entrySet();
}
public String get(Object key) {
return this.internalMap.get(key);
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public String put(String key, String value) {
return this.internalMap.put(key, value);
}
public void putAll(Map<? extends String, ? extends String> m) {
this.internalMap.putAll(m);
}
public String remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<String> values() {
return this.internalMap.values();
}
private void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}
}
The difference to a regular HashMap is only the constructor that populates it. Hope that clarifies it.
I've never seen that id property.. My beans in faces-config look like this:
<managed-bean>
<managed-bean-name>CurrentJob</managed-bean-name>
<managed-bean-class>com.domain.inventory.Job</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Technically managed beans should implement Serializable and have a blank constructor. So you should have something like this inside :
public ToolTipText() {
}
I THINK you can get away without the Serializable for somethings... I always implement though but I'm sure you need the no argument constructor.
Thanks to all that have responded and helped out here especially Stephan Wissel. I thought I would post my version of Stephan's code, pretty much the same. There are issues with making the class an ApplicationScope because you need to shut down the HTTP task to refresh and reload the Class. What I did was added a button to the custom control where I to the view of the tooltips where I do the CRUD stuff and in the button do WFSToolTip().clear() and it rebuilds the map. Pretty neat. My next task for this is try to do the CRUD using JAVA and update the map directly. At the moment though I need to move on to my next task.
My next task revolves around a very similar Class. I have a master database that contains all the basic design and code. Then I have one or more applications that use that code and store the documents in their own database that contains the forms and views for that specific application. In the master I have created one or more application documents. Each of these documents contains the AppName (the key value) then the Map value is an array (Vector) containing the ReplicaID of the Application Database and a few other pieces of information. My class the loads a Map entry for each Application and collects a bunch of other information about the application from several places and stores that in the Map Value. At this point then I can set Database db = thisClass.getDatabase("App Name"). so a single custom control can be used for any/all of the applications. Pretty cool. I think I could get to like this.
Anyway here is the code I'm using for the ToolTips - BTW It has taken an XPage with about 175 fields and 100+ tooltips from being painfully slow to being acceptable. The good thing about it is that the XPage is creating a process profile document and once created it is not frequently modified as an admin action - not an everyday user action.
Please feel free point out error, omitions or suggestions to the code:
package ca.workflo.wfsToolTip;
import lotus.domino.Base;
import lotus.domino.Session;
import lotus.domino.Database;
import lotus.domino.View;
import lotus.domino.NotesException;
import lotus.domino.ViewEntry;
import lotus.domino.ViewEntryCollection;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import com.ibm.xsp.extlib.util.ExtLibUtil;
public class ToolTipText implements Serializable, Map<String, String> {
private static final long serialVersionUID = 1L;
private Session s;
private String repID;
private Database db;
private Database helpDB;
private View helpView;
private ViewEntry ve;
private ViewEntry tVE;
private ViewEntryCollection veCol;
private final Map<String, String> internalMap = new HashMap<String, String>();
public ToolTipText() {
this.populateMap(internalMap);
}
private void populateMap(Map<String, String> theMap) {
try {
s = ExtLibUtil.getCurrentSession();
db = s.getCurrentDatabase();
repID = db.getProfileDocument("frmConfigProfile", "").getItemValue(
"WFSHelpRepID").firstElement().toString();
helpDB = s.getDbDirectory(null).openDatabaseByReplicaID(repID);
helpView = helpDB.getView("vwWFSToolTipHelp");
veCol = helpView.getAllEntries();
ve = veCol.getFirstEntry();
ViewEntry tVE = null;
while (ve != null) {
tVE = veCol.getNextEntry(ve);
Vector colVal = ve.getColumnValues();
theMap.put(colVal.get(0).toString(), colVal.get(1).toString());
recycleObjects(ve);
ve = tVE;
}
} catch (NotesException e) {
System.out.println(e.toString());
}finally{
recycleObjects(ve, tVE, veCol, helpView, helpDB);
}
}
public void clear() {
this.internalMap.clear();
this.populateMap(this.internalMap);
}
public boolean containsKey(Object key) {
return this.internalMap.containsKey(key);
}
public boolean containsValue(Object value) {
return this.internalMap.containsValue(value);
}
public Set<java.util.Map.Entry<String, String>> entrySet() {
return this.internalMap.entrySet();
}
public String get(Object key) {
try {
if (this.internalMap.containsKey(key)) {
return this.internalMap.get(key);
} else {
return "There is no Tooltip Help for " + key;
}
} catch (Exception e) {
return "error in tooltip get Object ";
}
}
public boolean isEmpty() {
return this.internalMap.isEmpty();
}
public Set<String> keySet() {
return this.internalMap.keySet();
}
public String put(String key, String value) {
return this.internalMap.put(key, value);
}
public void putAll(Map<? extends String, ? extends String> m) {
this.internalMap.putAll(m);
}
public String remove(Object key) {
return this.internalMap.remove(key);
}
public int size() {
return this.internalMap.size();
}
public Collection<String> values() {
return this.internalMap.values();
}
public static void recycleObjects(Object... args) {
for (Object o : args) {
if (o != null) {
if (o instanceof Base) {
try {
((Base) o).recycle();
} catch (Throwable t) {
// who cares?
}
}
}
}
}
}
I am trying to learn IOC principle from this screencast
Inversion of Control from First Principles - Top Gear Style
I tried do as per screencast but i get an error while AutomaticFactory try create an object of AutoCue. AutoCue class has contructor which takes IClock and not SystemClock. But my question is , in screencast IClock is resolved with SystemClock while inside AutomaticFactory .But in my code , IClock does not get resolved . Am i missing something ?
class Program
{
static void Main(string[] args)
{
//var clarkson = new Clarkson(new AutoCue(new SystemClock()), new Megaphone());
//var clarkson = ClarksonFactory.SpawnOne();
var clarkson = (Clarkson)AutomaticFactory.GetOne(typeof(Clarkson));
clarkson.SaySomething();
Console.Read();
}
}
public class AutomaticFactory
{
public static object GetOne(Type type)
{
var constructor = type.GetConstructors().Single();
var parameters = constructor.GetParameters();
if (!parameters.Any()) return Activator.CreateInstance(type);
var args = new List<object>();
foreach(var parameter in parameters)
{
var arg = GetOne(parameter.ParameterType);
args.Add(arg);
}
var result = Activator.CreateInstance(type, args.ToArray());
return result;
}
}
public class Clarkson
{
private readonly AutoCue _autocue;
private readonly Megaphone _megaphone;
public Clarkson(AutoCue autocue,Megaphone megaphone)
{
_autocue = autocue;
_megaphone =megaphone;
}
public void SaySomething()
{
var message = _autocue.GetCue();
_megaphone.Shout(message);
}
}
public class Megaphone
{
public void Shout(string message)
{
Console.WriteLine(message);
}
}
public interface IClock
{
DateTime Now { get; }
}
public class SystemClock : IClock
{
public DateTime Now { get { return DateTime.Now; } }
}
public class AutoCue
{
private readonly IClock _clock;
public AutoCue(IClock clock)
{
_clock = clock;
}
public string GetCue()
{
DateTime now = _clock.Now;
if (now.DayOfWeek == DayOfWeek.Sunday)
{
return "Its a sunday!";
}
else
{
return "I have to work!";
}
}
}
What you basically implemented is a small IoC container that is able to auto-wire object graphs. But your implementation is only able to create object graphs of concrete objects. This makes your code violate the Dependency Inversion Principle.
What's missing from the implementation is some sort of Register method that tells your AutomaticFactory that when confronted with an abstraction, it should resolve the registered implementation. That could look as follows:
private static readonly Dictionary<Type, Type> registrations =
new Dictionary<Type, Type>();
public static void Register<TService, TImplementation>()
where TImplementation : class, TService
where TService : class
{
registrations.Add(typeof(TService), typeof(TImplementation));
}
No you will have to do an adjustment to the GetOne method as well. You can add the following code at the start of the GetOne method:
if (registrations.ContainsKey(type))
{
type = registrations[type];
}
That will ensure that if the supplied type is registered in the AutomaticFactory as TService, the mapped TImplementation will be used and the factory will continue using this implementation as the type to build up.
This does mean however that you now have to explicitly register the mapping between IClock and SystemClock (which is a quite natural thing to do if you're working with an IoC container). You must make this mapping before the first instance is resolved from the AutomaticFactory. So you should add the following line to to the beginning of the Main method:
AutomaticFactory.Register<IClock, SystemClock>();