When I try to evaluate a boolean expression that contain a variable with null value or evaluate a undefined variable, the parser not work as I expected, it does not fail, rather than, it assume the null variable (or the undefined variable) as big negative number (I guess...).
Here the Test class I wrote to show this:
public class SpELTest {
#Test(expected = Exception.class)
public void evaluateNullVariable() {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("PERFORMANCE", null);
Boolean result = (Boolean)parser.parseExpression("#PERFORMANCE < 100").getValue(context);
assertTrue(result); // no expected result
}
#Test(expected = Exception.class)
public void evaluateUndefinedVariable() {
ExpressionParser parser = new SpelExpressionParser();
Boolean result = (Boolean)parser.parseExpression("#UNDEFINED < 100").getValue();
assertTrue(result); // no expected result
}
}
any idea of this behavior or how to avoid it?
"#PERFORMANCE == null ? false : #PERFORMANCE < 100"
Related
I am able to do the following :-
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'hi'.equals('hi')");
it evaluates to true.
Im failing at using variables in the expression ? for example:
If i want to :
Case 1 : use local variables
String str1 = "hi";
String str2 = "hi";
Expression exp = parser.parseExpression("str1.equals(str2)");
and Case2 : use field from a pojo :
Expression exp = parser.parseExpression("pojo1.getName().equals(pojo2.getName())");
How do i achieve case1 and 2 ? The above 2 examples are failing for me.. Case 1 evaluates to false and Case 2 throws an exception
You could do it like this:
Case 1:
String str1 = "hi";
String str2 = "hi";
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setVariable("str1", str1);
evaluationContext.setVariable("str2", str2);
ExpressionParser parser = new SpelExpressionParser();
boolean result = parser.parseExpression("#str1 eq #str2").getValue(evaluationContext, Boolean.class);
// result will be true
// also "#str1 == #str2" can be used
Case 2:
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.setVariable("pojo1Name", pojo1.getName());
evaluationContext.setVariable("pojo2Name", pojo2.getName());
ExpressionParser parser = new SpelExpressionParser();
boolean result = parser.parseExpression("#pojo1Name eq #pojo2Name").getValue(evaluationContext, Boolean.class);
Case 1.
You have to register variables with evaluation context, then use #str1 in the expression.
/**
* Set a named variable within this evaluation context to a specified value.
* #param name the name of the variable to set
* #param value the value to be placed in the variable
*/
void setVariable(String name, #Nullable Object value);
Case 2:
Use a BeanExpressionResolver wired up with the bean factory. If you implement BeanFactoryAware, use
#Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.resolver = ((ConfigurableListableBeanFactory) beanFactory).getBeanExpressionResolver();
this.expressionContext = new BeanExpressionContext((ConfigurableListableBeanFactory) beanFactory, null);
}
}
then call evaluate.
Beans are referenced with # - #someBean.someMethod().
I'm new to Kotlin (I have a Java background) and I can't seem to figure out how to check whether a string contains a match from a list of keywords.
What I want to do is check if a string contains a match from an array of keywords (case-insensitive please). If so, print out the keyword(s) that was matched and the string that contained the keyword. (I will be looping over a bunch of strings in a file).
Here's an MVE for starters:
val keywords = arrayOf("foo", "bar", "spam")
fun search(content: String) {
var match = <return an array of the keywords that content contained>
if(match.size > 0) {
println("Found match(es): " + match + "\n" + content)
}
}
fun main(args: Array<String>) {
var str = "I found food in the barn"
search(str) //should print out that foo and bar were a match
}
As a start (this ignores the 'match' variable and getting-a-list-of-keywords-matched), I tried using the following if statement according with what I found at this question,
if(Arrays.stream(keywords).parallel().anyMatch(content::contains))
but it put a squiggly line under "content" and gave me this error
None of the following functions can be called with the arguments
supplied: public operator fun CharSequence.contains(char: Char,
ignoreCase: Boolean = ...): Boolean defined in kotlin.text public
operator fun CharSequence.contains(other: CharSequence, ignoreCase:
Boolean = ...): Boolean defined in kotlin.text #InlineOnly public
inline operator fun CharSequence.contains(regex: Regex): Boolean
defined in kotlin.text
You can use the filter function to leave only those keywords contained in content:
val match = keywords.filter { it in content }
Here match is a List<String>. If you want to get an array in the result, you can add .toTypedArray() call.
in operator in the expression it in content is the same as content.contains(it).
If you want to have case insensitive match, you need to specify ignoreCase parameter when calling contains:
val match = keywords.filter { content.contains(it, ignoreCase = true) }
Another obvious choice is using a regex doing case-insensitive matching:
arrayOf("foo", "bar", "spam").joinToString(prefix = "(?i)", separator = "|").toRegex())
Glues together a pattern with a prefixed inline (?i) incase-sensitive modifier, and alternations between the keywords: (?i)foo|bar|spam
Sample Code:
private val keywords = arrayOf("foo", "bar", "spam")
private val pattern = keywords.joinToString(prefix = "(?i)", separator = "|")
private val rx = pattern.toRegex()
fun findKeyword(content: String): ArrayList<String> {
var result = ArrayList<String>()
rx.findAll(content).forEach { result.add(it.value) }
return result
}
fun main(args: Array<String>) {
println(findKeyword("Some spam and a lot of bar"));
}
The regex approach could be handy if you are after some more complex matching, e.g. non-/overlapping matches adding word boundaries \b, etc.
Here is my approach without Streams:
fun String.containsAnyOfIgnoreCase(keywords: List<String>): Boolean {
for (keyword in keywords) {
if (this.contains(keyword, true)) return true
}
return false
}
Usage:
"test string".containsAnyOfIgnoreCase(listOf("abc","test"))
I think Any is the efficient way.
fun findMatch(s: String, strings: List<String>): Boolean {
return strings.any { s.contains(it) }
}
fun main() {
val today = "Wednesday"
val weekend = listOf("Sat", "Sun")
println(if (findMatch(today, weekend)) "Yes" else "No") // No
}
reference: click here
I have the following data class
data class PuzzleBoard(val board: IntArray) {
val dimension by lazy { Math.sqrt(board.size.toDouble()).toInt() }
}
I read that data classes in Kotlin get equals()/hashcode() method for free.
I instantiated two objects.
val board1 = PuzzleBoard(intArrayOf(1,2,3,4,5,6,7,8,0))
val board2 = PuzzleBoard(intArrayOf(1,2,3,4,5,6,7,8,0))
But still, the following statements return false.
board1 == board2
board1.equals(board2)
In Kotlin data classes equality check, arrays, just like other classes, are compared using equals(...), which compares the arrays references, not the content. This behavior is described here:
So, whenever you say
arr1 == arr2
DataClass(arr1) == DataClass(arr2)
...
you get the arrays compared through equals(), i.e. referentially.
Given that,
val arr1 = intArrayOf(1, 2, 3)
val arr2 = intArrayOf(1, 2, 3)
println(arr1 == arr2) // false is expected here
println(PuzzleBoard(arr1) == PuzzleBoard(arr2)) // false too
To override this and have the arrays compared structurally, you can implement equals(...)+hashCode() in your data class using Arrays.equals(...) and Arrays.hashCode(...):
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as PuzzleBoard
if (!Arrays.equals(board, other.board)) return false
return true
}
override fun hashCode(): Int{
return Arrays.hashCode(board)
}
This code is what IntelliJ IDEA can automatically generate for non-data classes.
Another solution is to use List<Int> instead of IntArray. Lists are compared structurally, so that you won't need to override anything.
Kotlin implementation:
override fun equals(other: Any?): Boolean {
when (other) {
is User -> {
return this.userId == other.userId &&
this.userName == other.userName
}
else -> return false
}
}
For Data classes in Kotlin, hashcode() method will generate and return the same integer if parameters values are same for both objects.
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
println(user.hashCode().equals(secondUser.hashCode()))
println(user.hashCode().equals(thirdUser.hashCode()))
Running this code will return True and False as when we created secondUser object we have passed same argument as object user, so hashCode() integer generated for both of them will be same.
also if you will check this:
println(user.equals(thirdUser))
It will return false.
As per hashCode() method docs
open fun hashCode(): Int (source)
Returns a hash code value for the object. The general contract of
hashCode is:
Whenever it is invoked on the same object more than once, the hashCode
method must consistently return the same integer, provided no
information used in equals comparisons on the object is modified.
If two objects are equal according to the equals() method, then calling
the hashCode method on each of the two objects must produce the same
integer result.
For more details see this discussion here
In Kotlin, equals() behaves differently between List and Array, as you can see from code below:
val list1 = listOf(1, 2, 3)
val list2 = listOf(1, 2, 3)
val array1 = arrayOf(1, 2, 3)
val array2 = arrayOf(1, 2, 3)
//Side note: using a==b is the same as a.equals(b)
val areListsEqual = list1 == list2// true
val areArraysEqual = array1 == array2// false
List.equals() checks whether the two lists have the same size and contain the same elements in the same order.
Array.equals() simply does an instance reference check. Since we created two arrays, they point to different objects in memory, thus not considered equal.
Since Kotlin 1.1, to achieve the same behavior as with List, you can use Array.contentEquals().
Source: Array.contentEquals() docs ;
List.equals() docs
The board field in the PuzzleBoard class is an IntArray, when compiled it is turned into a primitive integer array. Individual array elements are never compared when checking the equality of primitive integer arrays. So calling equals on int array returns false as they are pointing to different objects. Eventually, this results in getting false in the equals() method, even though array elements are the same.
Byte code check
Looking at the decompiled java byte code, the Kotlin compiler generates some functions of data classes for us.
This includes,
copy() function
toString() function - takes form ClassName(var1=val1, var2=val2, ...)
hashCode() function
equals() function
Hash code is generated by adding the hash code of individual variables and multiplying by 31. The reason for multiplying is that it can be replaced with the bitwise operator and according to experimental results, 31 and other numbers like 33, 37, 39, 41, etc. gave fever clashes when multiplied.
Take a look at decompiled java byte code of the Kotlin class PuzzleBoard which reveals the secrets of data classes.
#Metadata(
mv = {1, 7, 1},
k = 1,
d1 = {"\u0000(\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0015\n\u0002\b\u0004\n\u0002\u0010\b\n\u0002\b\u0007\n\u0002\u0010\u000b\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\b\u0086\b\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\t\u0010\r\u001a\u00020\u0003HÆ\u0003J\u0013\u0010\u000e\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u0003HÆ\u0001J\u0013\u0010\u000f\u001a\u00020\u00102\b\u0010\u0011\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0012\u001a\u00020\bHÖ\u0001J\t\u0010\u0013\u001a\u00020\u0014HÖ\u0001R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u001b\u0010\u0007\u001a\u00020\b8FX\u0086\u0084\u0002¢\u0006\f\n\u0004\b\u000b\u0010\f\u001a\u0004\b\t\u0010\n¨\u0006\u0015"},
d2 = {"Lcom/aureusapps/androidpagingbasics/data/PuzzleBoard;", "", "board", "", "([I)V", "getBoard", "()[I", "dimension", "", "getDimension", "()I", "dimension$delegate", "Lkotlin/Lazy;", "component1", "copy", "equals", "", "other", "hashCode", "toString", "", "androidpagingbasics_debug"}
)
public final class PuzzleBoard {
#NotNull
private final Lazy dimension$delegate;
#NotNull
private final int[] board;
public final int getDimension() {
Lazy var1 = this.dimension$delegate;
Object var3 = null;
return ((Number)var1.getValue()).intValue();
}
#NotNull
public final int[] getBoard() {
return this.board;
}
public PuzzleBoard(#NotNull int[] board) {
Intrinsics.checkNotNullParameter(board, "board");
super();
this.board = board;
this.dimension$delegate = LazyKt.lazy((Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
return this.invoke();
}
public final int invoke() {
return (int)Math.sqrt((double)PuzzleBoard.this.getBoard().length);
}
}));
}
#NotNull
public final int[] component1() {
return this.board;
}
#NotNull
public final PuzzleBoard copy(#NotNull int[] board) {
Intrinsics.checkNotNullParameter(board, "board");
return new PuzzleBoard(board);
}
// $FF: synthetic method
public static PuzzleBoard copy$default(PuzzleBoard var0, int[] var1, int var2, Object var3) {
if ((var2 & 1) != 0) {
var1 = var0.board;
}
return var0.copy(var1);
}
#NotNull
public String toString() {
return "PuzzleBoard(board=" + Arrays.toString(this.board) + ")";
}
public int hashCode() {
int[] var10000 = this.board;
return var10000 != null ? Arrays.hashCode(var10000) : 0;
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof PuzzleBoard) {
PuzzleBoard var2 = (PuzzleBoard)var1;
if (Intrinsics.areEqual(this.board, var2.board)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
I have the following code:
public fun findSomeLikeThis(): ArrayList<T>? {
val result = Db4o.objectContainer()!!.queryByExample<T>(this as T) as Collection<T>
if (result == null) return null
return ArrayList(result)
}
If I call this like:
var list : ArrayList<Person>? = p1.findSomeLikeThis()
for (p2 in list) {
p2.delete()
p2.commit()
}
It would give me the error:
For-loop range must have an 'iterator()' method
Am I missing something here?
Your ArrayList is of nullable type. So, you have to resolve this. There are several options:
for (p2 in list.orEmpty()) { ... }
or
list?.let {
for (p2 in it) {
}
}
or you can just return an empty list
public fun findSomeLikeThis(): List<T> //Do you need mutable ArrayList here?
= (Db4o.objectContainer()!!.queryByExample<T>(this as T) as Collection<T>)?.toList().orEmpty()
try
for(p2 in 0 until list.count()) {
...
...
}
I also face this problem when I loop on some thing it is not an array.
Example
fun maximum(prices: Array<Int>){
val sortedPrices = prices.sort()
for(price in sortedPrices){ // it will display for-loop range must have iterator here (because `prices.sort` don't return Unit not Array)
}
}
This is different case to this question but hope it help
This can also happen in Android when you read from shared preferences and are getting a (potentially) nullable iterable object back like StringSet. Even when you provide a default, the compiler is not able to determine that the returned value will never actually be null. The only way I've found around this is by asserting that the returned expression is not null using !! operator, like this:
val prefs = PreferenceManager.getDefaultSharedPreferences(appContext)
val searches = prefs.getStringSet("saved_searches", setOf())!!
for (search in searches){
...
}
I am trying to create a matching game where one object in the array hitBoxes is matched to one object in the array hitBoxes2. I have tried to convert the instance name into a string and then used the substring method to match the LAST number in the instance name, if its a match they win. Right now I'm getting the error
TypeError: Error #1009: Cannot access a property or method of a null
object reference. at MethodInfo-499()
I'm wondering if anyone can help me. Thanks!
var left:String;
var correct:MovieClip = new Correct;
var isClicked:Boolean = false;
var leftClicked:int = 0;
p3.nextPage.buttonMode = true;
p3.nextPage.addEventListener(MouseEvent.CLICK, nextPage);
function nextPage(MouseEvent):void{
removeChild(p3);
}
var hitBoxes:Array = [p3.a1, p3.a2, p3.a3, p3.a4, p3.a5, p3.a6, p3.a7, p3.a8];
var hitBoxes2:Array = [p3.b1, p3.b2, p3.b3, p3.b4, p3.b5, p3.b6, p3.b7, p3.b8];
for (var h:int = 0; h < hitBoxes.length; h++){
hitBoxes[h].buttonMode = true;
hitBoxes[h].addEventListener(MouseEvent.CLICK, matchingLeft);
}
for (var h2:int = 0; h2 < hitBoxes2.length; h2++){
hitBoxes2[h2].buttonMode = true;
hitBoxes2[h2].addEventListener(MouseEvent.CLICK, matchingRight);
}
function matchingLeft(e:MouseEvent):void{
var left = String(e.currentTarget.name);
isClicked = true;
trace(left);
}
function matchingRight(e:MouseEvent):void{
var right:String = String(e.currentTarget.name);
trace(right);
if(isClicked == true && left.substring(3,3) == right.substring(3,3)){
trace("matched");
}
}
According to your code variable "left" is null at matchingRight method, because matchingLeft uses its local variable with name "left", and top-level "left" still has its default value.
also String.substring method is used incorrectly:
var name:String="p3.a1";
trace(name.substring(3, 3)); // this will always output empty string ""
trace(name.substring(4, 5)); // this will output "1" string
in conclusion I'd advise to use array indices (integers) instead of strings when calculating "matched" condition, substring operation and string comparison are CPU intensive.