I would like to repeatedly enter a number that is added to a linked list.
But there's an error in the code at line x = new Node():
No enclosing instance of type Main is accessible. Must qualify the allocation with an enclosing instance of type Main (e.g. x.new A() where x is an instance of Main).
Is there a way to fix my code?
static Node head;
static Node p;
static Node q;
static Node x;
class Node {
int data;
Node next;
public Node link;
// Constructor to create a new node
// Next is by default initialized
// as null
Node(int d) {
data = d;
next = null;
}
public Node() {
// TODO Auto-generated constructor stub
}
}
Two issues:
class Node should either be declared as static, or be moved to a separate file.
p = x should happen outside the else block, since it should get this value also when the if condition was true:
if(head == null) {
head = x;
} else {
p.link = x;
}
p = x;
Some remarks:
If you really want to use the Node constructor without arguments, then it is better to define explicitly what the new Node's properties should be:
public Node() {
data = 0;
next = null;
}
However, it would be better to not have this constructor signature at all, and only construct the object using the data as argument:
if(num != -999){
x = new Node(num);
...and now you don't need to do any of this any more:
x.data = num;
x.link = null;
The inner Node class is not static, meaning it belongs to a specific instance of the enclosing Main class. Since it doesn't refer to any instance methods this seems like it was not done intentionally. Make the class itself static (i.e., static class Node {) and you should be fine.
First, the class Node cannot be referenced from a static context. To fix this, make it static or move it to its own file. Second, the null pointer exception happens since you don't assign p in the special case where the list is empty.
Overall, I suggest that you clean up your class and use a more structured approach. Rename p to last to make it clear that this is a reference to the last element of the list. Move the functionality to add a node into its own method to make the code more readable. Use next in the Node class to point to the next node instead of link. Create an instance of the class where your head and last reference is defined and make them private. Use break inside the loop to only define the magic number (-999) once and exit the loop when it is entered.
The whole class could look like this:
public class CustomLinkedList {
private Node head = null;
private Node last = null;
static class Node {
int data;
Node next = null;
}
public void add(int num) {
Node x = new Node();
x.data = num;
if (this.head == null) {
this.head = x;
} else {
this.last.next = x;
}
this.last = x;
}
public static void main(String[] args) {
CustomLinkedList list = new CustomLinkedList();
int count = 0;
do {
try {
BufferedReader dataIn = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Enter number " + (++count) + ": ");
String strNum = dataIn.readLine();
int num = Integer.parseInt(strNum);
if (num != -999) {
list.add(num);
} else {
break; // exit loop
}
} catch (IOException e) {
System.out.print(e.getMessage());
}
} while (true);
}
}
Related
I have a List<List<String>> dataTableList and I would like to get a specific list from there and put it on my List<String> dataList so that I could loop through that specific lists' value and alter it.
However, whenever I try to do that,I always get an error of:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List.
Here's a sample of how I am trying to assign a specific list from dataTableList to dataList:
//First I looped through the List of Lists and called each list fetched as dataList
for(List<String> dataList : getTryLang().getDataTableList()){
//then, I created an iterator to be used later when I return the List I fetched with altered value
int iter = 0;
//next, I created a for-loop to iterate through the values of the List the I feched
for(int x; x < dataList.size(); x++){
//here, I formatted each value to amount or currency format.
dataList.set(x, getDataConvert().convertAmount(dataList.get(x)));
//finally, after I formatted everything, I returned it or set it to the specific index it's been located before
getTryLang().getDataTableList().set(iter, dataList);
}
iter++;
}
EDIT:
Here's my code and I modified some of it and didn't include some so that I could focus on expressing where the problem occurs.
Here's my TryLang.java:
#ManagedBean
#SessionScoped
public class TryLang implements Serializable {
public TryLang() {}
//declare
private List<List<String>> dataTableList;
//getter setter
public List<List<String>> getDataTableList() {
return dataTableList == null ? dataTableList = new ArrayList<>() : dataTableList;
}
public void setDataTableList(List<List<String>> dataTableList) {
this.dataTableList = dataTableList;
}
}
Then here's my BookOfAccountsController.java:
#ManagedBean
#RequestScoped
public class BooksOfAccountsController implements Serializable {
public BooksOfAccountsController() {}
//declare
#ManagedProperty(value = "#{dataConvert}")
private DataConvert dataConvert;
#ManagedProperty(value = "#{tryLang}")
private TryLang tryLang;
//getter setter NOTE: I wouldn't include other getter setters to shorten the code here :)
public TryLang getTryLang() {
return tryLang == null ? tryLang = new TryLang() : tryLang;
}
public void setTryLang(TryLang tryLang) {
this.tryLang = tryLang;
}
//I would just go straight to the method instead
public void runBooksOfAccounts() throws SystemException, SQLException {
//So there are dbCons here to connect on my DB and all. And I'll just go straight on where the List<List<String>> is being set
//Here's where the List<List<String>> is being set
getTryLang().setDataTableList(getCemf().getFdemf().createEntityManager().createNativeQuery("SELECT crj.* FROM crj_rep crj").getResultList());
getTryLang().setDataTableColumns(getCemf().getFdemf().createEntityManager().createNativeQuery("SELECT col.column_name FROM information_schema.columns col WHERE table_schema = 'public' AND table_name = 'crj_rep'").getResultList());
for (int x = 0; x < getTryLang().getDataTableColumns().size(); x++) {
try {
Integer.parseInt(getTryLang().getDataTableColumns().get(x));
getTryLang().getDataTableColumns().set(x, getDataConvert().accountCodeConvert(getTryLang().getDataTableColumns().get(x)));
//then here is where the error points at
for (List<String> dataList : getTryLang().getDataTableList()) {
try{
int iter = 0;
dataList.set(x, getDataConvert().convertAmount(new BigDecimal(dataList.get(x))));
getTryLang().getDataTableList().set(iter, dataList);
iter++;
}catch(ClassCastException ne){
System.out.println("cannot convert " + ne);
}
}
} catch (NumberFormatException ne) {
//print the error
}
}
}
}
I wrote a static class that auto-increments the id of a RealmObject by 1.
public class AutoIncrementKey {
public static int Next(Class<? extends RealmObject> c)
{
Realm realm = Realm.getDefaultInstance();
Number maxId = realm.where(c).max("id");
realm.close();
if(maxId == null)
{ // no object exists, so return 0
return 0;
}
return maxId.intValue() + 1;
}
}
However, when I set the default value of a RealmObject's ID like so:
#PrimaryKey private int id = AutoIncrementKey.Next(PresetSelect.class);
It never works! Specifically the first time it goes to create a new class via realm.createObject(IExtendRealmObject.class) the value is 0, but AutoIncrementKey.Next(...) returns the id as 1!
So id is never set to 1. It's always 0, and trying to create more objects causes it to throw an error "index already exists: 0"
What gives?
The AutoIncrementKey.Next() function IS being called. It IS finding the next key to be 1. The value returned simply isn't carried through though.
Edit:
So now that I've managed to create more than one object in my Realm, I'm finding that setting the id to a default value isn't the only issue.
Setting ANY member of a class extending RealmObject with a default value is IGNORED. Whats the deal with that?
That's because instead of
realm.createObject(IExtendRealmObject.class)
You're supposed to use
realm.createObject(IExtendRealmObject.class, primaryKeyValue)
But I think your method
public class AutoIncrementKey {
public static int Next(Class<? extends RealmObject> c)
{
Realm realm = Realm.getDefaultInstance();
Number maxId = realm.where(c).max("id");
realm.close();
if(maxId == null)
{ // no object exists, so return 0
return 0;
}
return maxId.intValue() + 1;
}
}
Would be more stable as
public class AutoIncrementKey {
public static int Next(Realm realm, Class<? extends RealmModel> c)
{
Number maxId = realm.where(c).max("id");
if(maxId == null)
{ // no object exists, so return 0
return 0;
}
return maxId.intValue() + 1; // why not long?
}
}
If you meet the condition that when you call AutoIncrementKey.Next(realm, Some.class), then a write transaction is in progress.
Hell, you might even add
public class AutoIncrementKey {
public static int Next(Realm realm, Class<? extends RealmModel> c)
{
if(!realm.isInTransaction()) {
throw new IllegalStateException("Realm is not in a transaction.");
}
// continue as mentioned
It should work well for your needs
I am new to java.
The following code has NullPointerException , I really don't know what's going on. Seems like I was trying to access a null pointer. But I have no idea where I was accessing the null pointer. (If it is the "head" node part, I didn't use its object slot in this code I think.)
Thanks so much!
class Deque<Base>
{
private class Node
{
private Base object;
private Node right; //point to the front Node
private Node left; //Point to the rear Node
//Constrcutor for Node
private Node(Base object, Node left, Node right)
{
this.object = object;
this.left = left;
this.right = right;
}
}
private Node head;
//Constructor for Deque
public Deque()
{
head = new Node(null,head,head);
}
//--------------------------------------
public void enqueueFront(Base object)
{
head.right = new Node(object, head, head.right);
head.right.right.left = head.right;
//Node temp = head.right;
//temp.left = head.right.right;
}
//--------------------------------------------
public void enqueueRear(Base object)
{
//Node temp = head.left;
head.left = new Node(object, head.left, head);
//temp.right = head.left;
head.left.left.right = head.left;
}
//------------------------------------------------
public Base dequeueFront()
{
if (isEmpty())
{
throw new IllegalStateException();
}
else
{
Base a = head.right.object;
Node temp = head.right.right;
temp.left = head;
head.right = temp;
return a;
}
}
//---------------------------------------------------
public Base dequeueRear()
{
if (isEmpty())
{
throw new IllegalStateException();
}
else
{
Base b = head.left.object;
Node temp = head.left.left;
temp.right = head;
head.left = temp;
return b;
}
}
//----------------------------------------------------
public boolean isEmpty()
{
return ((head.left == head)&&(head.right == head));
}
}
class Driver10
{
public static void main(String [] args)
{
Deque<Integer> a = new Deque<Integer>();
//int temp;
//a.dequeueFront();
a.enqueueFront(2);
//a.enqueueFront(3);
//a.enqueueFront(4);
//a.enqueueRear(6);
//a.enqueueRear(7);
//front: 4,3,2
//rear: 7,6
//temp = a.dequeueRear();
// System.out.println(temp); // 7
//System.out.println(a.dequeueRear()); // 6
//System.out.println(a.dequeueRear()); // 2
//System.out.println(a.dequeueFront()); //4
//System.out.println(a.dequeueRear()); // 3
//a.dequeueRear();
}
}
This statement looks very suspicious:
head.right.right.left = head.right;
It doesn't look likely that head.right.right would be anything other than null at the point that the code tries to access its left member.
It looks like you need to assign a new node to head.right.right before trying to assign to head.right.right.left
Multiple Issues:
The most relevants are:
head = new Node(null,head,head);
When you instantiate Dequeue, you assign bydefault to right and left as null as head is null. So post assigning right and left as null, you are assigning node to head.
Post that you try to enqueue an element and you try:
head.right = new Node(object, head, head.right);
head.right.right.left = head.right;
Here you are setting right as new node but see that head.right is still null as mentioned above. Post this statement your queue with right elements (just right for now) would be like:
head -> node -> null
Then you do
head.right.right.left = head.right;
Here you have valid node.right, but node.right.right is null as above and you are trying to access left for null which is where you are getting NullPointerException.
You might need to debug and make sure if you want something like or not:
head.right.left = head.right;
I've been dealing with a score corruption error for few days with no apparent reason. The error appears only on FULL_ASSERT mode and it is not related to the constraints defined on the drools file.
Following is the error :
014-07-02 14:51:49,037 [SwingWorker-pool-1-thread-4] TRACE Move index (0), score (-4/-2450/-240/-170), accepted (false) for move (EMP4#START => EMP2).
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the workingScore (-3/-1890/-640/-170) is not the uncorruptedScore (-3/-1890/-640/-250) after completedAction (EMP3#EMP4 => EMP4):
The corrupted scoreDirector has 1 ConstraintMatch(s) which are in excess (and should not be there):
com.abcdl.be.solver/MinimizeTotalTime/level3/[org.drools.core.reteoo.InitialFactImpl#4dde85f0]=-170
The corrupted scoreDirector has 1 ConstraintMatch(s) which are missing:
com.abcdl.be.solver/MinimizeTotalTime/level3/[org.drools.core.reteoo.InitialFactImpl#4dde85f0]=-250
Check your score constraints.
The error appears every time after several steps are completed for no apparent reason.
I'm developing a software to schedule several tasks considering time and resources constraints.
The whole process is represented by a directed tree diagram such that the nodes of the graph represent the tasks and the edges, the dependencies between the tasks.
To do this, the planner change the parent node of each node until he finds the best solution.
The node is the planning entity and its parent the planning variable :
#PlanningEntity(difficultyComparatorClass = NodeDifficultyComparator.class)
public class Node extends ProcessChain {
private Node parent; // Planning variable: changes during planning, between score calculations.
private String delay; // Used to display the delay for nodes of type "And"
private int id; // Used as an identifier for each node. Different nodes cannot have the same id
public Node(String name, String type, int time, int resources, String md, int id)
{
super(name, "", time, resources, type, md);
this.id = id;
}
public Node()
{
super();
this.delay = "";
}
public String getDelay() {
return delay;
}
public void setDelay(String delay) {
this.delay = delay;
}
#PlanningVariable(valueRangeProviderRefs = {"parentRange"}, strengthComparatorClass = ParentStrengthComparator.class, nullable = false)
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
/*public String toString()
{
if(this.type.equals("AND"))
return delay;
if(!this.md.isEmpty())
return Tools.excerpt(name+" : "+this.md);
return Tools.excerpt(name);
}*/
public String toString()
{
if(parent!= null)
return Tools.excerpt(name) +"#"+parent;
else
return Tools.excerpt(name);
}
public boolean equals( Object o ) {
if (this == o) {
return true;
} else if (o instanceof Node) {
Node other = (Node) o;
return new EqualsBuilder()
.append(name, other.name)
.append(id, other.id)
.isEquals();
} else {
return false;
}
}
public int hashCode() {
return new HashCodeBuilder()
.append(name)
.append(id)
.toHashCode();
}
// ************************************************************************
// Complex methods
// ************************************************************************
public int getStartTime()
{
try{
return Graph.getInstance().getNode2times().get(this).getFirst();
}
catch(NullPointerException e)
{
System.out.println("getStartTime() is null for " + this);
}
return 10;
}
public int getEndTime()
{ try{
return Graph.getInstance().getNode2times().get(this).getSecond();
}
catch(NullPointerException e)
{
System.out.println("getEndTime() is null for " + this);
}
return 10;
}
#ValueRangeProvider(id = "parentRange")
public Collection<Node> getPossibleParents()
{
Collection<Node> nodes = new ArrayList<Node>(Graph.getInstance().getNodes());
nodes.remove(this); // We remove this node from the list
if(Graph.getInstance().getParentsCount(this) > 0)
nodes.remove(Graph.getInstance().getParents(this)); // We remove its parents from the list
if(Graph.getInstance().getChildCount(this) > 0)
nodes.remove(Graph.getInstance().getChildren(this)); // We remove its children from the list
if(!nodes.contains(Graph.getInstance().getNt()))
nodes.add(Graph.getInstance().getNt());
return nodes;
}
/**
* The normal methods {#link #equals(Object)} and {#link #hashCode()} cannot be used because the rule engine already
* requires them (for performance in their original state).
* #see #solutionHashCode()
*/
public boolean solutionEquals(Object o) {
if (this == o) {
return true;
} else if (o instanceof Node) {
Node other = (Node) o;
return new EqualsBuilder()
.append(name, other.name)
.append(id, other.id)
.isEquals();
} else {
return false;
}
}
/**
* The normal methods {#link #equals(Object)} and {#link #hashCode()} cannot be used because the rule engine already
* requires them (for performance in their original state).
* #see #solutionEquals(Object)
*/
public int solutionHashCode() {
return new HashCodeBuilder()
.append(name)
.append(id)
.toHashCode();
}
}
Each move must update the graph by removing the previous edge and adding the new edge from the node to its parent, so i'm using a custom change move :
public class ParentChangeMove implements Move{
private Node node;
private Node parent;
private Graph g = Graph.getInstance();
public ParentChangeMove(Node node, Node parent) {
this.node = node;
this.parent = parent;
}
public boolean isMoveDoable(ScoreDirector scoreDirector) {
List<Dependency> dep = new ArrayList<Dependency>(g.getDependencies());
dep.add(new Dependency(parent.getName(), node.getName()));
return !ObjectUtils.equals(node.getParent(), parent) && !g.detectCycles(dep) && !g.getParents(node).contains(parent);
}
public Move createUndoMove(ScoreDirector scoreDirector) {
return new ParentChangeMove(node, node.getParent());
}
public void doMove(ScoreDirector scoreDirector) {
scoreDirector.beforeVariableChanged(node, "parent"); // before changes are made
//The previous edge is removed from the graph
if(node.getParent() != null)
{
Dependency d = new Dependency(node.getParent().getName(), node.getName());
g.removeEdge(g.getDep2link().get(d));
g.getDependencies().remove(d);
g.getDep2link().remove(d);
}
node.setParent(parent); // the move
//The new edge is added on the graph (parent ==> node)
Link link = new Link();
Dependency d = new Dependency(parent.getName(), node.getName());
g.addEdge(link, parent, node);
g.getDependencies().add(d);
g.getDep2link().put(d, link);
g.setStepTimes();
scoreDirector.afterVariableChanged(node, "parent"); // after changes are made
}
public Collection<? extends Object> getPlanningEntities() {
return Collections.singletonList(node);
}
public Collection<? extends Object> getPlanningValues() {
return Collections.singletonList(parent);
}
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o instanceof ParentChangeMove) {
ParentChangeMove other = (ParentChangeMove) o;
return new EqualsBuilder()
.append(node, other.node)
.append(parent, other.parent)
.isEquals();
} else {
return false;
}
}
public int hashCode() {
return new HashCodeBuilder()
.append(node)
.append(parent)
.toHashCode();
}
public String toString() {
return node + " => " + parent;
}
}
The graph does define multiple methods that are used by the constraints to calculate the score for each solution like the following :
rule "MinimizeTotalTime" // Minimize the total process time
when
eval(true)
then
scoreHolder.addSoftConstraintMatch(kcontext, 1, -Graph.getInstance().totalTime());
end
On other environment modes, the error does not appear but the best score calculated is not equal to the actual score.
I don't have any clue as to where the problem could come from. Note that i already checked all my equals and hashcode methods.
EDIT : Following ge0ffrey's proposition, I used collect CE in "MinimizeTotalTime" rule to check if the error comes again :
rule "MinimizeTotalTime" // Minimize the total process time
when
ArrayList() from collect(Node())
then
scoreHolder.addSoftConstraintMatch(kcontext, 0, -Graph.getInstance().totalTime());
end
At this point, no error appears and everything seems ok. But when I use "terminate early", I get the following error :
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the solution's score (-9133) is not the uncorruptedScore (-9765).
Also, I have a rule that doesn't use any method from the Graph class and seems to respect the incremental score calculation but returns another score corruption error.
The purpose of the rule is to make sure that we don't use more resources that available:
rule "addMarks" //insert a Mark each time a task starts or ends
when
Node($startTime : getStartTime(), $endTime : getEndTime())
then
insertLogical(new Mark($startTime));
insertLogical(new Mark($endTime));
end
rule "resourcesLimit" // At any time, The number of resources used must not exceed the total number of resources available
when
Mark($startTime: time)
Mark(time > $startTime, $endTime : time)
not Mark(time > $startTime, time < $endTime)
$total : Number(intValue > Global.getInstance().getAvailableResources() ) from
accumulate(Node(getEndTime() >=$endTime, getStartTime()<= $startTime, $res : resources), sum($res))
then
scoreHolder.addHardConstraintMatch(kcontext, 0, (Global.getInstance().getAvailableResources() - $total.intValue()) * ($endTime - $startTime) );
end
Following is the error :
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Score corruption: the workingScore (-193595) is not the uncorruptedScore (-193574) after completedAction (DWL_CM_XX_101#DWL_PA_XX_180 => DWL_PA_XX_180):
The corrupted scoreDirector has 4 ConstraintMatch(s) which are in excess (and should not be there):
com.abcdl.be.solver/resourcesLimit/level0/[43.0, 2012, 1891]=-2783
com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1870, 1805]=-1625
com.abcdl.be.solver/resourcesLimit/level0/[46.0, 1805, 1774]=-806
com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1774, 1762]=-300
The corrupted scoreDirector has 3 ConstraintMatch(s) which are missing:
com.abcdl.be.solver/resourcesLimit/level0/[43.0, 2012, 1901]=-2553
com.abcdl.be.solver/resourcesLimit/level0/[45.0, 1870, 1762]=-2700
com.abcdl.be.solver/resourcesLimit/level0/[44.0, 1901, 1891]=-240
Check your score constraints.
A score rule that has a LHS of just "eval(true)" is inherently broken. Either that constraint is always broken, for the exact same weight, and there really is no reason to evaluate it. Or it is sometimes broken (or always broken but for different weights) and then the rule needs to refire accordingly.
Problem: the return value of Graph.getInstance().totalTime() changes as the planning variables change value. But Drools just looks at the LHS as planning variables change and it sees that nothing in the LHS has changed so there's no need to re-evaluate that score rule, when the planning variables change. Note: this is called incremental score calculation (see docs), which is a huge performance speedup.
Subproblem: The method Graph.getInstance().totalTime() is inherently not incremental.
Fix: translate that totalTime() function into a DRL function based on Node selections. You 'll probably need to use accumulate. If that's too hard (because it's a complex calculation of the critical path or so), try it anyway (for incremental score calculation's sake) or try a LHS that does a collect over all Nodes (which is like eval(true) but it will be refired every time.
Just came across the latest build of Mono.CSharp and love the promise it offers.
Was able to get the following all worked out:
namespace XAct.Spikes.Duo
{
class Program
{
static void Main(string[] args)
{
CompilerSettings compilerSettings = new CompilerSettings();
compilerSettings.LoadDefaultReferences = true;
Report report = new Report(new Mono.CSharp.ConsoleReportPrinter());
Mono.CSharp.Evaluator e;
e= new Evaluator(compilerSettings, report);
//IMPORTANT:This has to be put before you include references to any assemblies
//our you;ll get a stream of errors:
e.Run("using System;");
//IMPORTANT:You have to reference the assemblies your code references...
//...including this one:
e.Run("using XAct.Spikes.Duo;");
//Go crazy -- although that takes time:
//foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
//{
// e.ReferenceAssembly(assembly);
//}
//More appropriate in most cases:
e.ReferenceAssembly((typeof(A).Assembly));
//Exception due to no semicolon
//e.Run("var a = 1+3");
//Doesn't set anything:
//e.Run("a = 1+3;");
//Works:
//e.ReferenceAssembly(typeof(A).Assembly);
e.Run("var a = 1+3;");
e.Run("A x = new A{Name=\"Joe\"};");
var a = e.Evaluate("a;");
var x = e.Evaluate("x;");
//Not extremely useful:
string check = e.GetVars();
//Note that you have to type it:
Console.WriteLine(((A) x).Name);
e = new Evaluator(compilerSettings, report);
var b = e.Evaluate("a;");
}
}
public class A
{
public string Name { get; set; }
}
}
And that was fun...can create a variable in the script's scope, and export the value.
There's just one last thing to figure out... how can I get a value in (eg, a domain entity that I want to apply a Rule script on), without using a static (am thinking of using this in a web app)?
I've seen the use compiled delegates -- but that was for the previous version of Mono.CSharp, and it doesn't seem to work any longer.
Anybody have a suggestion on how to do this with the current version?
Thanks very much.
References:
* Injecting a variable into the Mono.CSharp.Evaluator (runtime compiling a LINQ query from string)
* http://naveensrinivasan.com/tag/mono/
I know it's almost 9 years later, but I think I found a viable solution to inject local variables. It is using a static variable but can still be used by multiple evaluators without collision.
You can use a static Dictionary<string, object> which holds the reference to be injected. Let's say we are doing all this from within our class CsharpConsole:
public class CsharpConsole {
public static Dictionary<string, object> InjectionRepository {get; set; } = new Dictionary<string, object>();
}
The idea is to temporarily place the value in there with a GUID as key so there won't be any conflict between multiple evaluator instances. To inject do this:
public void InjectLocal(string name, object value, string type=null) {
var id = Guid.NewGuid().ToString();
InjectionRepository[id] = value;
type = type ?? value.GetType().FullName;
// note for generic or nested types value.GetType().FullName won't return a compilable type string, so you have to set the type parameter manually
var success = _evaluator.Run($"var {name} = ({type})MyNamespace.CsharpConsole.InjectionRepository[\"{id}\"];");
// clean it up to avoid memory leak
InjectionRepository.Remove(id);
}
Also for accessing local variables there is a workaround using Reflection so you can have a nice [] accessor with get and set:
public object this[string variable]
{
get
{
FieldInfo fieldInfo = typeof(Evaluator).GetField("fields", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
var fields = fieldInfo.GetValue(_evaluator) as Dictionary<string, Tuple<FieldSpec, FieldInfo>>;
if (fields != null)
{
if (fields.TryGetValue(variable, out var tuple) && tuple != null)
{
var value = tuple.Item2.GetValue(_evaluator);
return value;
}
}
}
return null;
}
set
{
InjectLocal(variable, value);
}
}
Using this trick, you can even inject delegates and functions that your evaluated code can call from within the script. For instance, I inject a print function which my code can call to ouput something to the gui console window:
public delegate void PrintFunc(params object[] o);
public void puts(params object[] o)
{
// call the OnPrint event to redirect the output to gui console
if (OnPrint!=null)
OnPrint(string.Join("", o.Select(x => (x ?? "null").ToString() + "\n").ToArray()));
}
This puts function can now be easily injected like this:
InjectLocal("puts", (PrintFunc)puts, "CsInterpreter2.PrintFunc");
And just be called from within your scripts:
puts(new object[] { "hello", "world!" });
Note, there is also a native function print but it directly writes to STDOUT and redirecting individual output from multiple console windows is not possible.