In my project, I have a class Device like this:
public class Device {
private Set<String> abilities = new HashSet<String>();
public Device(Set<String> abilities) {
this.abilities = abilities;
}
public Set<String> getAbilities() {
return abilities;
}
}
I am initializing this Device class with:
Set<String> device1Abilities = new HashSet<String>();
device1Abilities.add("BadgeReader");
device1Abilities.add("TemperatureSensor");
device1Abilities.add("xyz");
Device d1 = new Device(device1Abilities);
In my stringTemplateFile, I am retrieving abilities using
$device.abilities :{ sc | abilities.add("$sc$"); }$
which will generates following code =>
abilities.add("BadgeReader");
abilities.add("TemperatureSensor");
abilities.add("xyz");
Now, my requirement is ----- I do not want to generate this line of code:
abilities.add("xyz");
What condition should I specify in
$device.abilities :{ sc | abilities.add("$sc$"); }$
so that it does not generate that line?
That computation really belongs in the model so you should do the filtering of the list that you passed to the template. The template should not figure out which data to display. It should display the data that your model says it should display. hope this helps.
See here. You are using an anonymous sub-template abilities.add("$sc$");. Instead you can use a template call with sc as parameter. And there you can test on "xyz". Though maybe someone with more StringTemplate experience knows a shorter notation.
Related
tl;dr:
How do/can I store the function-handles of multiple js-functions in java for using them later? Currently I have two ideas:
Create multipe ScriptEngine instances, each containing one loaded function. Store them in a map by column, multiple entries per column in a list. Looks like a big overhead depending on how 'heavy' a ScriptEngine instance is...
Some Javascript solution to append methods of the same target field to an array. Dont know yet how to access that from the java-side, but also dont like it. Would like to keep the script files as stupid as possible.
var test1 = test1 || [];
test1.push(function(input) { return ""; });
???
Ideas or suggestions?
Tell me more:
I have a project where I have a directory containing script files (javascript, expecting more than hundred files, will grow in future). Those script files are named like: test1;toupper.js, test1;trim.js and test2;capitalize.js. The name before the semicolon is the column/field that the script will be process and the part after the semicolon is a human readable description what the file does (simplified example). So in this example there are two scripts that will be assigned to the "test1" column and one script to the "test2" column. The js-function template basically looks like:
function process(input) { return ""; };
My idea is, to load (and evaluate/compile) all script files at server-startup and then use the loaded functions by column when they are needed. So far, so good.
I can load/evaluate a single function with the following code. Example uses GraalVM, but should be reproducable with other languages too.
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("graal.js");
final Invocable invocable = (Invocable) engine;
engine.eval("function process(arg) { return arg.toUpperCase(); };");
var rr0 = invocable.invokeFunction("process", "abc123xyz"); // rr0 = ABC123XYZ
But when I load/evaluate the next function with the same name, the previous one will be overwritten - logically, since its the same function name.
engine.eval("function process(arg) { return arg + 'test'; };");
var rr1 = invocable.invokeFunction("process", "abc123xyz"); // rr1 = abc123xyztest
This is how I would do it.
The recommended way to use Graal.js is via the polyglot API: https://www.graalvm.org/reference-manual/embed-languages/
Not the same probably would work with the ScriptEngine API, but here's the example using the polyglot API.
Wrap the function definition in ()
return the functions to Java
Not pictured, but you probably build a map from the column name to a list of functions to invoke on it.
Call the functions on the data.
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
public class HelloPolyglot {
public static void main(String[] args) {
System.out.println("Hello Java!");
try (Context context = Context.create()) {
Value toUpperCase = context.eval("js", "(function process(arg) { return arg.toUpperCase(); })");
Value concatTest = context.eval("js", "(function process(arg) { return arg + 'test'; })");
String text = "HelloWorld";
text = toUpperCase.execute(text).asString();
text = concatTest.execute(text).asString();
System.out.println(text);
}
}
}
Now, Value.execute() returns a Value, which I for simplicity coerce to a Java String with asString(), but you don't have to do that and you can operate on Value (here's the API for Value: https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Value.html).
I've got a database of playerdata that has some pre-existing fields from previous versions of the program. Example out-dated document:
{
"playername": "foo"
}
but a player document generated under the new version would look like this:
{
"playername": "bar",
"playercurrency": 20
}
the issue is that if I try to query playercurrency on foo I get a NullPointerException because playercurrency doesn't exist for foo. I want to add the playercurrency field to foo without disturbing any other data that could be stored in foo. I've tried some code using $exists Example:
players.updateOne(new Document("playername", "foo"), new Document("$exists", new Document("playername", "")));
players.updateOne(new Document("playername", "foo"), new Document("$exists", new Document("playercurrency", 20)));
My thought is that it updates only playercurrency because it doesn't exist and it would leave playername alone becuase it exists. I might be using exists horribly wrong, and if so please do let me know because this is one of my first MongoDB projects and I would like to learn as much as I possibly can.
Do you have to do this with java? Whenever I add a new field that I want to be required I just use the command line to migrate all existing documents. This will loop through all players that don't have a playercurrency and set it to 0 (change to whatever default you want):
db.players.find({playercurrency:null}).forEach(function(player) {
player.playercurrency = 0; // or whatever default value
db.players.save(player);
});
This will result in you having the following documents:
{
"playername" : "foo",
"playercurrency" : 0
}
{
"playername" : "bar",
"playercurrency" : 20
}
So I know that it is normally frowned upon on answering your own question, but nobody really posted what I ended up doing I would like to take this time to thank #Mark Watson for answering and ultimately guiding me to finding my answer.
Since checking if a certain field is null doesn't work in the MongoDB Java Driver I needed to find a different way to know when something is primed for an update. So after a little bit of research I stumbled upon this question which helped me come up with this code:
private static void updateValue(final String name, final Object defaultValue, final UUID key) {
if (!exists(name, key)) {
FindIterable iterable = players.find(new Document("_id", key));
iterable.forEach(new Block<Document>() {
#Override
public void apply(Document document) {
players.updateOne(new Document("_id", key), new Document("$set", new Document(name, defaultValue)));
}
});
}
}
private static boolean exists(String name, UUID key) {
Document query = new Document(name, new Document("$exists", true)).append("_id", key);
return players.count(query) == 1;
}
Obviously this is a little specialized to what I wanted to do, but with little revisions it can be easliy changed to work with anything you might need. Make sure to replace players with your Collection object.
I am trying to use a 'select' form element in Play! 2.3, and cannot get it to work. What do I supply to the template? Currently I have this:
public static Result add(Long sensorId) {
Form<Action> myForm = Form.form(Action.class);
Sensor sensor = Sensor.find.byId(sensorId);
Action action = new Action();
action.actionUp = true;
action.sensor = sensor;
myForm.fill(action);
HashMap<String, String> devices = new HashMap<>();
for(Device device : Device.find.all()){
devices.put(device.id.toString(), device.name);
}
return ok(editView.render(myForm, action, devices));
}
And the template:
#(myForm: Form[models.Action], action: models.Action, deviceList: HashMap[String, String])
#helper.select(myForm("device"), deviceList,'_label -> "Perform on device")
But that doesn't work as it expects a Seq[(String, String)].
I cannot find a way to create that in Java though... any help is very much appreciated!
Form helper won't know, if you directly place the deviceList in select and in the end, it expects options from you. That is the reason, it shows this error expects a Seq[(String, String)].
To solve this you must wrap the deviceList by options to let helper know deviceList is the options for select.
So it should be like following.
#helper.select(myForm("device"), options(deviceList),'_label -> "Perform on device")
I am getting started with StringTemplate 4 and I am trying to create a template from a simple string stored in a database. I use something like this:
STGroup group = new STGroupString(null, someTemplateString, '$', '$');
ST st = group.getInstanceOf(someTemplateName);
st.add(someAttribute, someValue);
Now everything works fine if I define all or less than the attribute defined for the template someTemplateName. Now if I try to add an attribute that doesn't exist, I get the following exception:
no such attribute: fake
java.lang.IllegalArgumentException: no such attribute: fake
...
which makes sense. However, it seems like there's no way for me to know beforehand which attributes are defined for the template someTemplateName. I was expecting to find something like:
bool isDef = st.isDefined(someAttribute);
but there's no such method. Am I correct? Is there any way around this?
The documentation for CompiledST states tokens is only for debug. Not sure what that means.
ST template = new ST("Hello <username>, how are you? Using <if(condition)>expression<endif> in condition works, and repeating <username> is not a problem.");
Set<String> expressions = new HashSet<String>();
TokenStream tokens = template.impl.tokens;
for (int i = 0; i < tokens.range(); i++) {
Token token = tokens.get(i);
if (token.getType() == STLexer.ID) {
expressions.add(token.getText());
}
}
Gives you the Strings username and condition.
You can use st.impl.formalArguments to access the Map<String, FormalArgument> where the arguments are defined. Note that for some templates this field will be null.
I am trying to find an HTML template solution for a Java web application. I need the flexibility to load templates from whatever sources I choose as most of them will likely be in a custom database. In my search I stumbled upon StringTemplate 4, however all of the examples I see require that the user put template files on disk.
I have noticed that STGroup can be instantiated without specifying a file or directory, however using the defineTemplate method does not seem to be a substitute for using file based templates. Unfortunately in all my tests with defineTemplate I have failed to get attributes to work. This all feels like I'm guessing in the dark.
Is StringTemplate the right library for this? Is there another one that would work better?
I'm starting to consider developing my own.
I figured it out...
import org.stringtemplate.v4.*;
import net.sf.json.*;
class STExampleApp {
public static void main(String args[]) {
String template = "decl(type, name, value) ::= \"<type> <name><init(value)>;\"\n"
+ "init(v) ::= \"<if(v)> = <v><endif>\"";
STGroup views = new STGroupString(template);
ST decl = views.getInstanceOf("decl");
decl.add("type", "int");
decl.add("name", "x");
decl.add("value", 12);
System.out.println(decl.render());
}
}
No file loading necessary. I learned this from: How to format decimal numbers with StringTemplate (ST) in Java?
I would just pass the template to a ST() constructor like this:
#Test public void testNullAttr() throws Exception {
String template = "hi <name>!";
ST st = new ST(template);
String expected =
"hi !";
String result = st.render();
assertEquals(expected, result);
}