/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.painless; import org.apache.lucene.util.Constants; import java.util.HashMap; import java.util.Locale; import java.util.Map; import static java.util.Collections.singletonMap; import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS; public class StringTests extends ScriptTestCase { public void testAppend() { // boolean assertEquals("cat" + true, exec("String s = \"cat\"; return s + true;")); // byte assertEquals("cat" + (byte)3, exec("String s = \"cat\"; return s + (byte)3;")); // short assertEquals("cat" + (short)3, exec("String s = \"cat\"; return s + (short)3;")); // char assertEquals("cat" + 't', exec("String s = \"cat\"; return s + 't';")); assertEquals("cat" + (char)40, exec("String s = \"cat\"; return s + (char)40;")); // int assertEquals("cat" + 2, exec("String s = \"cat\"; return s + 2;")); // long assertEquals("cat" + 2L, exec("String s = \"cat\"; return s + 2L;")); // float assertEquals("cat" + 2F, exec("String s = \"cat\"; return s + 2F;")); // double assertEquals("cat" + 2.0, exec("String s = \"cat\"; return s + 2.0;")); // String assertEquals("cat" + "cat", exec("String s = \"cat\"; return s + s;")); // boolean assertEquals("cat" + true, exec("String s = 'cat'; return s + true;")); // byte assertEquals("cat" + (byte)3, exec("String s = 'cat'; return s + (byte)3;")); // short assertEquals("cat" + (short)3, exec("String s = 'cat'; return s + (short)3;")); // char assertEquals("cat" + 't', exec("String s = 'cat'; return s + 't';")); assertEquals("cat" + (char)40, exec("String s = 'cat'; return s + (char)40;")); // int assertEquals("cat" + 2, exec("String s = 'cat'; return s + 2;")); // long assertEquals("cat" + 2L, exec("String s = 'cat'; return s + 2L;")); // float assertEquals("cat" + 2F, exec("String s = 'cat'; return s + 2F;")); // double assertEquals("cat" + 2.0, exec("String s = 'cat'; return s + 2.0;")); // String assertEquals("cat" + "cat", exec("String s = 'cat'; return s + s;")); } public void testAppendMultiple() { assertEquals("cat" + true + "abc" + null, exec("String s = \"cat\"; return s + true + 'abc' + null;")); } public void testAppendMany() { for (int i = MAX_INDY_STRING_CONCAT_ARGS - 5; i < MAX_INDY_STRING_CONCAT_ARGS + 5; i++) { doTestAppendMany(i); } } private void doTestAppendMany(int count) { StringBuilder script = new StringBuilder("String s = \"cat\"; return s"); StringBuilder result = new StringBuilder("cat"); for (int i = 1; i < count; i++) { final String s = String.format(Locale.ROOT, "%03d", i); script.append(" + '").append(s).append("'.toString()"); result.append(s); } final String s = script.toString(); assertTrue("every string part should be separatly pushed to stack.", Debugger.toString(s).contains(String.format(Locale.ROOT, "LDC \"%03d\"", count/2))); assertEquals(result.toString(), exec(s)); } public void testNestedConcats() { assertEquals("foo1010foo", exec("String s = 'foo'; String x = '10'; return s + Integer.parseInt(x + x) + s;")); } public void testStringAPI() { assertEquals("", exec("return new String();")); assertEquals('x', exec("String s = \"x\"; return s.charAt(0);")); assertEquals(120, exec("String s = \"x\"; return s.codePointAt(0);")); assertEquals(0, exec("String s = \"x\"; return s.compareTo(\"x\");")); assertEquals("xx", exec("String s = \"x\"; return s.concat(\"x\");")); assertEquals(true, exec("String s = \"xy\"; return s.endsWith(\"y\");")); assertEquals(2, exec("String t = \"abcde\"; return t.indexOf(\"cd\", 1);")); assertEquals(false, exec("String t = \"abcde\"; return t.isEmpty();")); assertEquals(5, exec("String t = \"abcde\"; return t.length();")); assertEquals("cdcde", exec("String t = \"abcde\"; return t.replace(\"ab\", \"cd\");")); assertEquals(false, exec("String s = \"xy\"; return s.startsWith(\"y\");")); assertEquals("e", exec("String t = \"abcde\"; return t.substring(4, 5);")); assertEquals(97, ((char[])exec("String s = \"a\"; return s.toCharArray();"))[0]); assertEquals("a", exec("String s = \" a \"; return s.trim();")); assertEquals('x', exec("return \"x\".charAt(0);")); assertEquals(120, exec("return \"x\".codePointAt(0);")); assertEquals(0, exec("return \"x\".compareTo(\"x\");")); assertEquals("xx", exec("return \"x\".concat(\"x\");")); assertEquals(true, exec("return \"xy\".endsWith(\"y\");")); assertEquals(2, exec("return \"abcde\".indexOf(\"cd\", 1);")); assertEquals(false, exec("return \"abcde\".isEmpty();")); assertEquals(5, exec("return \"abcde\".length();")); assertEquals("cdcde", exec("return \"abcde\".replace(\"ab\", \"cd\");")); assertEquals(false, exec("return \"xy\".startsWith(\"y\");")); assertEquals("e", exec("return \"abcde\".substring(4, 5);")); assertEquals(97, ((char[])exec("return \"a\".toCharArray();"))[0]); assertEquals("a", exec("return \" a \".trim();")); assertEquals("", exec("return new String();")); assertEquals('x', exec("String s = 'x'; return s.charAt(0);")); assertEquals(120, exec("String s = 'x'; return s.codePointAt(0);")); assertEquals(0, exec("String s = 'x'; return s.compareTo('x');")); assertEquals("xx", exec("String s = 'x'; return s.concat('x');")); assertEquals(true, exec("String s = 'xy'; return s.endsWith('y');")); assertEquals(2, exec("String t = 'abcde'; return t.indexOf('cd', 1);")); assertEquals(false, exec("String t = 'abcde'; return t.isEmpty();")); assertEquals(5, exec("String t = 'abcde'; return t.length();")); assertEquals("cdcde", exec("String t = 'abcde'; return t.replace('ab', 'cd');")); assertEquals(false, exec("String s = 'xy'; return s.startsWith('y');")); assertEquals("e", exec("String t = 'abcde'; return t.substring(4, 5);")); assertEquals(97, ((char[])exec("String s = 'a'; return s.toCharArray();"))[0]); assertEquals("a", exec("String s = ' a '; return s.trim();")); assertEquals('x', exec("return 'x'.charAt(0);")); assertEquals(120, exec("return 'x'.codePointAt(0);")); assertEquals(0, exec("return 'x'.compareTo('x');")); assertEquals("xx", exec("return 'x'.concat('x');")); assertEquals(true, exec("return 'xy'.endsWith('y');")); assertEquals(2, exec("return 'abcde'.indexOf('cd', 1);")); assertEquals(false, exec("return 'abcde'.isEmpty();")); assertEquals(5, exec("return 'abcde'.length();")); assertEquals("cdcde", exec("return 'abcde'.replace('ab', 'cd');")); assertEquals(false, exec("return 'xy'.startsWith('y');")); assertEquals("e", exec("return 'abcde'.substring(4, 5);")); assertEquals(97, ((char[])exec("return 'a'.toCharArray();"))[0]); assertEquals("a", exec("return ' a '.trim();")); } public void testStringAndCharacter() { assertEquals('c', exec("return (char)\"c\"")); assertEquals('c', exec("return (char)'c'")); assertEquals("c", exec("return (String)(char)\"c\"")); assertEquals("c", exec("return (String)(char)'c'")); assertEquals('c', exec("String s = \"c\"; (char)s")); assertEquals('c', exec("String s = 'c'; (char)s")); ClassCastException expected = expectScriptThrows(ClassCastException.class, false, () -> { assertEquals("cc", exec("return (String)(char)\"cc\"")); }); assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char].")); expected = expectScriptThrows(ClassCastException.class, false, () -> { assertEquals("cc", exec("return (String)(char)'cc'")); }); assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char].")); expected = expectScriptThrows(ClassCastException.class, () -> { assertEquals('c', exec("String s = \"cc\"; (char)s")); }); assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char].")); expected = expectScriptThrows(ClassCastException.class, () -> { assertEquals('c', exec("String s = 'cc'; (char)s")); }); assertTrue(expected.getMessage().contains("Cannot cast [String] with length greater than one to [char].")); } public void testDefConcat() { assertEquals("a" + (byte)2, exec("def x = 'a'; def y = (byte)2; return x + y")); assertEquals("a" + (short)2, exec("def x = 'a'; def y = (short)2; return x + y")); assertEquals("a" + (char)2, exec("def x = 'a'; def y = (char)2; return x + y")); assertEquals("a" + 2, exec("def x = 'a'; def y = (int)2; return x + y")); assertEquals("a" + 2L, exec("def x = 'a'; def y = (long)2; return x + y")); assertEquals("a" + 2F, exec("def x = 'a'; def y = (float)2; return x + y")); assertEquals("a" + 2D, exec("def x = 'a'; def y = (double)2; return x + y")); assertEquals("ab", exec("def x = 'a'; def y = 'b'; return x + y")); assertEquals((byte)2 + "a", exec("def x = 'a'; def y = (byte)2; return y + x")); assertEquals((short)2 + "a", exec("def x = 'a'; def y = (short)2; return y + x")); assertEquals((char)2 + "a", exec("def x = 'a'; def y = (char)2; return y + x")); assertEquals(2 + "a", exec("def x = 'a'; def y = (int)2; return y + x")); assertEquals(2L + "a", exec("def x = 'a'; def y = (long)2; return y + x")); assertEquals(2F + "a", exec("def x = 'a'; def y = (float)2; return y + x")); assertEquals(2D + "a", exec("def x = 'a'; def y = (double)2; return y + x")); assertEquals("anull", exec("def x = 'a'; def y = null; return x + y")); assertEquals("nullb", exec("def x = null; def y = 'b'; return x + y")); expectScriptThrows(NullPointerException.class, () -> { exec("def x = null; def y = null; return x + y"); }); } public void testDefCompoundAssignment() { assertEquals("a" + (byte)2, exec("def x = 'a'; x += (byte)2; return x")); assertEquals("a" + (short)2, exec("def x = 'a'; x += (short)2; return x")); assertEquals("a" + (char)2, exec("def x = 'a'; x += (char)2; return x")); assertEquals("a" + 2, exec("def x = 'a'; x += (int)2; return x")); assertEquals("a" + 2L, exec("def x = 'a'; x += (long)2; return x")); assertEquals("a" + 2F, exec("def x = 'a'; x += (float)2; return x")); assertEquals("a" + 2D, exec("def x = 'a'; x += (double)2; return x")); assertEquals("ab", exec("def x = 'a'; def y = 'b'; x += y; return x")); assertEquals("anull", exec("def x = 'a'; x += null; return x")); assertEquals("nullb", exec("def x = null; x += 'b'; return x")); expectScriptThrows(NullPointerException.class, () -> { exec("def x = null; def y = null; x += y"); }); } public void testComplexCompoundAssignment() { Map<String, Object> params = new HashMap<>(); Map<String, Object> ctx = new HashMap<>(); ctx.put("_id", "somerandomid"); params.put("ctx", ctx); assertEquals("somerandomid.somerandomid", exec("ctx._id += '.' + ctx._id", params, false)); assertEquals("somerandomid.somerandomid", exec("String x = 'somerandomid'; x += '.' + x")); assertEquals("somerandomid.somerandomid", exec("def x = 'somerandomid'; x += '.' + x")); } public void testAppendStringIntoMap() { assertEquals("nullcat", exec("def a = new HashMap(); a.cat += 'cat'")); } public void testBase64Augmentations() { assertEquals("Y2F0", exec("'cat'.encodeBase64()")); assertEquals("cat", exec("'Y2F0'.decodeBase64()")); assertEquals("6KiA6Kqe", exec("'\u8A00\u8A9E'.encodeBase64()")); assertEquals("\u8A00\u8A9E", exec("'6KiA6Kqe'.decodeBase64()")); String rando = randomRealisticUnicodeOfLength(between(5, 1000)); assertEquals(rando, exec("params.rando.encodeBase64().decodeBase64()", singletonMap("rando", rando), true)); } public void testJava9StringConcatBytecode() { assumeTrue("Needs Java 9 to test indified String concat", Constants.JRE_IS_MINIMUM_JAVA9); assertNotNull(WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE); assertBytecodeExists("String s = \"cat\"; return s + true + 'abc' + null;", "INVOKEDYNAMIC concat(Ljava/lang/String;ZLjava/lang/String;Ljava/lang/Object;)Ljava/lang/String;"); } }