');
144 |
145 | function addTd(text) {
146 | tr.append($('').html(text));
147 | }
148 |
149 | addTd(value.name);
150 |
151 | $.each(DataSizeToView, function (index, dataSize) {
152 | var time = getTime(value, dataSize.value, property);
153 | addTd(time);
154 | });
155 |
156 | tbody.append(tr);
157 | });
158 | table.append(tbody);
159 |
160 | $(elem).empty().append(table);
161 | }
162 |
163 | function getOrUpdate(map, name, defaultValue) {
164 | var v = map[name] || defaultValue || {};
165 | map[name] = v;
166 | return v;
167 | }
168 |
169 | function convertData(list) {
170 | // {
171 | // name: javaConcat,
172 | // values: {
173 | // 7: {avg, p0...},
174 | // 101: {avg, p0...}
175 | // }
176 | // }
177 | //
178 | var map = {};
179 |
180 | $.each(list, function (index, entry) {
181 | // code from Benchmarks!
182 | var type = entry.params
183 | ? entry.params.arg
184 | : '';
185 |
186 | var name = extractName(entry.benchmark);
187 | var pm = entry.primaryMetric;
188 |
189 | var timesByStringLength = getOrUpdate(map, name, {name: name, values: {}}).values;
190 | timesByStringLength[type] = {
191 | avg: pm.score,
192 | p0: pm.scorePercentiles['0.0'],
193 | p50: pm.scorePercentiles['50.0'],
194 | p95: pm.scorePercentiles['95.0'],
195 | p100: pm.scorePercentiles['100.0']
196 | };
197 | });
198 |
199 | var result = [];
200 | $.each(Methods, function (index, method) {
201 | result.push(map[method.name] || {name: method.name});
202 | });
203 |
204 | return result;
205 | }
206 |
207 | function extractName(benchmark) {
208 | //'com.komanov.stringformat.jmh.ManyParamsBenchmark.concat'
209 |
210 | var index = benchmark.lastIndexOf('.');
211 | if (index == -1) {
212 | throw new Error('Expected a dot in a benchmark: ' + benchmark);
213 | }
214 | return benchmark.substring(index + 1);
215 | }
216 |
217 | function getDataForChart(property) {
218 | var result = [];
219 |
220 | var header = ['string length'];
221 | $.each(data, function (index, value) {
222 | header.push(value.name);
223 | });
224 | result.push(header);
225 |
226 | $.each(DataSizeToView, function (index, dataSize) {
227 | var line = [dataSize.name];
228 | $.each(data, function (index, value) {
229 | var time = getTime(value, dataSize.value, property);
230 | line.push(time);
231 | });
232 | result.push(line);
233 | });
234 |
235 | return result;
236 | }
237 |
238 | function getTime(value, dataSize, property) {
239 | return Math.floor(((value.values || {})[dataSize] || {})[property] || 0.0);
240 | }
241 | }
242 |
243 | $(document).ready(function () {
244 | loadGoogleCharts(loadJmhResult);
245 | });
246 |
247 | function loadGoogleCharts(onLoadCallback) {
248 | google.charts.load('current', {'packages': ['bar']});
249 | google.charts.setOnLoadCallback(onLoadCallback);
250 | }
251 |
252 | function loadJmhResult() {
253 | var url = 'jmh-result.json';
254 | $.getJSON(url, function (list) {
255 | loadScalaSerializationChartsPage(list);
256 | });
257 | }
258 |
--------------------------------------------------------------------------------
/jmh-run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 |
3 | ec() {
4 | echo $* 1>&2
5 | $*
6 | }
7 |
8 | ec mvn clean install
9 |
10 | ec java -jar scala-string-format-test/target/benchmarks.jar -rf json -rff jmh-result.json > jmh.log
11 |
12 | ec mv jmh-result.json docs/
13 | ec mv jmh.log docs/
14 |
15 | echo "Don't forget to push docs!"
16 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.komanov
6 | scala-string-format-all
7 | 1.0-SNAPSHOT
8 | pom
9 |
10 |
11 | scala-string-format
12 | scala-string-format-core
13 | scala-string-format-test
14 |
15 |
16 |
17 | UTF-8
18 | 1.16
19 | default
20 | 1.8
21 | benchmarks
22 |
23 |
24 |
25 |
26 | scala-tools.org
27 | Scala-tools Maven2 Repository
28 | http://scala-tools.org/repo-releases
29 |
30 |
31 |
32 |
33 |
34 | scala-tools.org
35 | Scala-tools Maven2 Repository
36 | http://scala-tools.org/repo-releases
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | org.apache.maven.plugins
45 | maven-compiler-plugin
46 | 3.5.1
47 |
48 | ${javac.target}
49 | ${javac.target}
50 | ${javac.target}
51 | -unchecked
52 | -deprecation
53 | -proc:none
54 |
55 |
56 |
57 | org.apache.maven.plugins
58 | maven-surefire-plugin
59 | 2.19.1
60 |
61 | ${project.basedir}/src/test/scala
62 |
63 |
64 |
65 | tests
66 | test
67 |
68 | test
69 |
70 |
71 |
72 |
73 |
74 | net.alchim31.maven
75 | scala-maven-plugin
76 | 3.2.2
77 |
78 | ${project.basedir}/src/test/scala
79 |
80 | -deprecation
81 | -feature
82 | -Xmax-classfile-name
83 | 240
84 |
85 | -deprecation
86 | ${javac.target}
87 | ${javac.target}
88 |
89 |
90 |
91 | scala-compile-first
92 | process-resources
93 |
94 | add-source
95 | compile
96 |
97 |
98 |
99 | scala-test-compile
100 | process-test-resources
101 |
102 | testCompile
103 |
104 |
105 |
106 | process-sources
107 |
108 | compile
109 |
110 |
111 |
112 |
113 |
114 | org.apache.maven.plugins
115 | maven-release-plugin
116 |
117 |
118 | org.apache.maven.plugins
119 | maven-resources-plugin
120 | 2.6
121 |
122 | ${project.build.sourceEncoding}
123 | true
124 |
125 |
126 |
127 | org.apache.maven.plugins
128 | maven-jar-plugin
129 | 2.4
130 |
131 |
132 |
133 | true
134 | true
135 |
136 |
137 | ${project.version}
138 | ${project.version}
139 | ${buildNumber}
140 | ${project.artifactId}
141 | ${maven.build.timestamp}
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | org.codehaus.mojo
152 | buildnumber-maven-plugin
153 | 1.3
154 |
155 |
156 | org.apache.maven.plugins
157 | maven-dependency-plugin
158 | 2.8
159 |
160 |
161 | org.apache.maven.plugins
162 | maven-compiler-plugin
163 |
164 |
165 | net.alchim31.maven
166 | scala-maven-plugin
167 |
168 |
169 | org.apache.maven.plugins
170 | maven-jar-plugin
171 |
172 |
173 | org.apache.maven.plugins
174 | maven-surefire-plugin
175 |
176 |
177 | org.apache.maven.plugins
178 | maven-failsafe-plugin
179 |
180 |
181 | org.apache.maven.plugins
182 | maven-resources-plugin
183 |
184 |
185 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/scala-string-format-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.komanov
8 | scala-string-format-core
9 | 1.0-SNAPSHOT
10 |
11 |
12 | scala-string-format-all
13 | com.komanov
14 | 1.0-SNAPSHOT
15 |
16 |
17 |
18 |
19 | org.scala-lang
20 | scala-library
21 | 2.12.0
22 |
23 |
24 | org.slf4j
25 | slf4j-api
26 | 1.7.21
27 |
28 |
29 | com.komanov
30 | scala-string-format
31 | 1.0-SNAPSHOT
32 |
33 |
34 |
35 |
36 |
37 | org.specs2
38 | specs2-core_2.12
39 | 3.8.6
40 | test
41 |
42 |
43 | org.specs2
44 | specs2-matcher-extra_2.12
45 | 3.8.6
46 | test
47 |
48 |
49 | org.specs2
50 | specs2-mock_2.12
51 | 3.8.6
52 | test
53 |
54 |
55 | org.specs2
56 | specs2-junit_2.12
57 | 3.8.6
58 | test
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/scala-string-format-core/src/main/scala/com/komanov/stringformat/InputArg.java:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat;
2 |
3 | public enum InputArg {
4 | Tiny(1, ""),
5 | VeryShort(1, "12345"),
6 | Short(100, "string__10"),
7 | Medium(10000, "string________________________32"),
8 | Long(100000, "string___________________________________________________________________________________________100"),
9 | VeryLong(10000000, "string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495"),
10 | VeryLongSizeMiss(Integer.MAX_VALUE, "string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495"),
11 | /*IDEA*/;
12 |
13 | public final int value1;
14 | public final String value2;
15 |
16 | InputArg(int value1, String value2) {
17 | this.value1 = value1;
18 | this.value2 = value2;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/scala-string-format-core/src/main/scala/com/komanov/stringformat/JavaFormats.java:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat;
2 |
3 | import org.slf4j.helpers.MessageFormatter;
4 |
5 | import java.text.MessageFormat;
6 | import java.util.Locale;
7 |
8 | public class JavaFormats {
9 | public static String concat(int value1, String value2, Object nullObject) {
10 | return value1 + "a" + value2 + "b" + value2 + nullObject;
11 | }
12 |
13 | public static String stringFormat(int value1, String value2, Object nullObject) {
14 | return String.format(Locale.ENGLISH, "%da%sb%s%s", value1, value2, value2, nullObject);
15 | }
16 |
17 | public static String messageFormat(int value1, String value2, Object nullObject) {
18 | return MessageFormat.format("{0,number,#}a{1}b{2}{3}", value1, value2, value2, nullObject);
19 | }
20 |
21 | public static String slf4j(int value1, String value2, Object nullObject) {
22 | return MessageFormatter
23 | .arrayFormat("{}a{}b{}{}", new Object[]{value1, value2, value2, nullObject})
24 | .getMessage();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scala-string-format-core/src/main/scala/com/komanov/stringformat/ScalaFormats.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | import com.komanov.stringformat.macros.MacroConcat._
4 |
5 | object ScalaFormats {
6 |
7 | def concat(value1: Int, value2: String, nullObject: Object): String = {
8 | value1 + "a" + value2 + "b" + value2 + nullObject
9 | }
10 |
11 | def optimizedConcat1(value1: Int, value2: String, nullObject: Object): String = {
12 | OptimizedConcatenation1.concat(Int.box(value1), "a", value2, "b", value2, nullObject)
13 | }
14 |
15 | def optimizedConcat2(value1: Int, value2: String, nullObject: Object): String = {
16 | OptimizedConcatenation2.concat(Int.box(value1), "a", value2, "b", value2, nullObject)
17 | }
18 |
19 | def optimizedConcatMacros(value1: Int, value2: String, nullObject: Object): String = {
20 | so"${value1}a${value2}b$value2$nullObject"
21 | }
22 |
23 | def sInterpolator(value1: Int, value2: String, nullObject: Object): String = {
24 | s"${value1}a${value2}b$value2$nullObject"
25 | }
26 |
27 | def fInterpolator(value1: Int, value2: String, nullObject: Object): String = {
28 | f"${value1}a${value2}b$value2$nullObject"
29 | }
30 |
31 | def rawInterpolator(value1: Int, value2: String, nullObject: Object): String = {
32 | raw"${value1}a${value2}b$value2$nullObject"
33 | }
34 |
35 | def sfiInterpolator(value1: Int, value2: String, nullObject: Object): String = {
36 | sfi"${value1}a${value2}b$value2$nullObject"
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/scala-string-format-core/src/test/scala/com/komanov/stringformat/FormatsTest.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | import org.specs2.mutable.SpecificationWithJUnit
4 | import org.specs2.specification.core.Fragment
5 |
6 | class FormatsTest extends SpecificationWithJUnit {
7 |
8 | val formats: List[(String, (Int, String, Object) => String)] = List(
9 | "javaConcat" -> JavaFormats.concat,
10 | "stringFormat" -> JavaFormats.stringFormat,
11 | "messageFormat" -> JavaFormats.messageFormat,
12 | "slf4j" -> JavaFormats.slf4j,
13 | "scalaConcat" -> ScalaFormats.concat,
14 | "optimizedConcat1" -> ScalaFormats.optimizedConcat1,
15 | "optimizedConcat2" -> ScalaFormats.optimizedConcat2,
16 | "optimizedConcatMacros" -> ScalaFormats.optimizedConcatMacros,
17 | "sInterpolator" -> ScalaFormats.sInterpolator,
18 | "fInterpolator" -> ScalaFormats.fInterpolator,
19 | "rawInterpolator" -> ScalaFormats.rawInterpolator,
20 | "sfiInterpolator" -> ScalaFormats.sfiInterpolator
21 | )
22 |
23 | Fragment.foreach(formats) { case (name, f) =>
24 | s"$name" should {
25 | "product the same result as JavaConcat" >> {
26 | f(1, "str", null) must beEqualTo(JavaFormats.concat(1, "str", null))
27 | f(1, null, "str") must beEqualTo(JavaFormats.concat(1, null, "str"))
28 | }
29 | }
30 | }
31 |
32 | val formatsWithInputArgs = for {
33 | (name, f) <- formats
34 | arg <- InputArg.values
35 | } yield (name, arg, f)
36 |
37 | Fragment.foreach(formatsWithInputArgs) { case (name, arg, f) =>
38 | s"$name" should {
39 | s"product the same result as JavaConcat for $arg" >> {
40 | f(arg.value1, arg.value2, null) must beEqualTo(JavaFormats.concat(arg.value1, arg.value2, null))
41 | }
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/scala-string-format-core/src/test/scala/com/komanov/stringformat/InputArgApp.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | object InputArgApp extends App {
4 |
5 | for (a <- InputArg.values()) {
6 | println(s"$a: ${JavaFormats.concat(a.value1, a.value2, null).length}")
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/scala-string-format-test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.komanov.jmh
6 | scala-string-format-test
7 | 1.0
8 | jar
9 |
10 |
11 | scala-string-format-all
12 | com.komanov
13 | 1.0-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.openjdk.jmh
19 | jmh-core
20 | ${jmh.version}
21 |
22 |
23 | org.openjdk.jmh
24 | jmh-generator-annprocess
25 | ${jmh.version}
26 | provided
27 |
28 |
29 | com.komanov
30 | scala-string-format-core
31 | 1.0-SNAPSHOT
32 |
33 |
34 |
35 |
36 | 3.0.4
37 |
38 |
39 |
40 |
41 |
42 | org.codehaus.mojo
43 | build-helper-maven-plugin
44 | 1.8
45 |
46 |
47 | add-source
48 | generate-sources
49 |
50 | add-source
51 |
52 |
53 |
54 | ${project.basedir}/src/main/scala
55 | ${project.basedir}/target/generated-sources/jmh
56 |
57 |
58 |
59 |
60 |
61 |
62 |
65 |
66 |
67 | org.codehaus.mojo
68 | exec-maven-plugin
69 | 1.2.1
70 |
71 |
72 | process-sources
73 |
74 | java
75 |
76 |
77 | true
78 | org.openjdk.jmh.generators.bytecode.JmhBytecodeGenerator
79 |
80 | ${project.basedir}/target/classes/
81 | ${project.basedir}/target/generated-sources/jmh/
82 | ${project.basedir}/target/classes/
83 | ${jmh.generator}
84 |
85 |
86 |
87 |
88 |
89 |
90 | org.openjdk.jmh
91 | jmh-generator-bytecode
92 | ${jmh.version}
93 |
94 |
95 |
96 |
97 |
100 |
101 |
102 | org.apache.maven.plugins
103 | maven-shade-plugin
104 | 2.2
105 |
106 |
107 | package
108 |
109 | shade
110 |
111 |
112 | ${uberjar.name}
113 |
114 |
115 | org.openjdk.jmh.Main
116 |
117 |
118 |
119 |
120 |
124 | *:*
125 |
126 | META-INF/*.SF
127 | META-INF/*.DSA
128 | META-INF/*.RSA
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | maven-resources-plugin
139 | 2.6
140 |
141 |
142 | maven-site-plugin
143 | 3.3
144 |
145 |
146 | maven-source-plugin
147 | 2.2.1
148 |
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/scala-string-format-test/src/main/scala/com/komanov/stringformat/jmh/Benchmarks.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat.jmh
2 |
3 | import java.util.concurrent.TimeUnit
4 |
5 | import com.komanov.stringformat.{InputArg, JavaFormats, ScalaFormats}
6 | import org.openjdk.jmh.annotations._
7 |
8 | @State(Scope.Benchmark)
9 | @BenchmarkMode(Array(Mode.AverageTime))
10 | @OutputTimeUnit(TimeUnit.NANOSECONDS)
11 | @Fork(value = 2, jvmArgs = Array("-Xmx2G"))
12 | @Measurement(iterations = 7, time = 3, timeUnit = TimeUnit.SECONDS)
13 | @Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS)
14 | abstract class BenchmarkBase
15 |
16 | class ManyParamsBenchmark extends BenchmarkBase {
17 |
18 | @Param
19 | var arg: InputArg = InputArg.Tiny
20 |
21 | var nullObject: Object = null
22 |
23 | @Benchmark
24 | def javaConcat(): String = {
25 | JavaFormats.concat(arg.value1, arg.value2, nullObject)
26 | }
27 |
28 | @Benchmark
29 | def scalaConcat(): String = {
30 | ScalaFormats.concat(arg.value1, arg.value2, nullObject)
31 | }
32 |
33 | @Benchmark
34 | def stringFormat(): String = {
35 | JavaFormats.stringFormat(arg.value1, arg.value2, nullObject)
36 | }
37 |
38 | @Benchmark
39 | def messageFormat(): String = {
40 | JavaFormats.messageFormat(arg.value1, arg.value2, nullObject)
41 | }
42 |
43 | @Benchmark
44 | def slf4j(): String = {
45 | JavaFormats.slf4j(arg.value1, arg.value2, nullObject)
46 | }
47 |
48 | @Benchmark
49 | def concatOptimized1(): String = {
50 | ScalaFormats.optimizedConcat1(arg.value1, arg.value2, nullObject)
51 | }
52 |
53 | @Benchmark
54 | def concatOptimized2(): String = {
55 | ScalaFormats.optimizedConcat2(arg.value1, arg.value2, nullObject)
56 | }
57 |
58 | @Benchmark
59 | def concatOptimizedMacros(): String = {
60 | ScalaFormats.optimizedConcatMacros(arg.value1, arg.value2, nullObject)
61 | }
62 |
63 | @Benchmark
64 | def sInterpolator(): String = {
65 | ScalaFormats.sInterpolator(arg.value1, arg.value2, nullObject)
66 | }
67 |
68 | @Benchmark
69 | def fInterpolator(): String = {
70 | ScalaFormats.fInterpolator(arg.value1, arg.value2, nullObject)
71 | }
72 |
73 | @Benchmark
74 | def rawInterpolator(): String = {
75 | ScalaFormats.rawInterpolator(arg.value1, arg.value2, nullObject)
76 | }
77 |
78 | @Benchmark
79 | def sfiInterpolator(): String = {
80 | ScalaFormats.sfiInterpolator(arg.value1, arg.value2, nullObject)
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/scala-string-format-test/src/main/scala/com/komanov/stringformat/jmh/NewStringBenchmark.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat.jmh
2 |
3 | import com.komanov.stringformat.FastStringFactory
4 | import org.openjdk.jmh.annotations.Benchmark
5 |
6 | object NewStringBenchmarkData {
7 | val chars = new Array[Char](1006)
8 | val sb = new java.lang.StringBuilder(chars.length)
9 | .append(chars)
10 | }
11 |
12 | class NewStringBenchmark extends BenchmarkBase {
13 |
14 | @Benchmark
15 | def baseline: String = {
16 | ""
17 | }
18 |
19 | @Benchmark
20 | def newString: String = {
21 | new String(NewStringBenchmarkData.chars)
22 | }
23 |
24 | @Benchmark
25 | def fastString: String = {
26 | FastStringFactory.fastNewString(NewStringBenchmarkData.chars)
27 | }
28 |
29 | @Benchmark
30 | def sbToString: String = {
31 | NewStringBenchmarkData.sb.toString
32 | }
33 |
34 | @Benchmark
35 | def fastSb: String = {
36 | FastStringFactory.fastNewString(NewStringBenchmarkData.sb)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/scala-string-format-test/src/main/scala/com/komanov/stringformat/jmh/SimpleBenchmarks.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat.jmh
2 |
3 | import org.openjdk.jmh.annotations.Benchmark
4 |
5 | class EmptyStringBenchmark extends BenchmarkBase {
6 |
7 | @Benchmark
8 | def baseline: String = {
9 | ""
10 | }
11 |
12 | @Benchmark
13 | def sInterpolator: String = {
14 | s""
15 | }
16 |
17 | @Benchmark
18 | def sfiInterpolator: String = {
19 | import com.komanov.stringformat.macros.MacroConcat._
20 | sfi""
21 | }
22 | }
23 |
24 | class ConstStringBenchmark extends BenchmarkBase {
25 |
26 | @Benchmark
27 | def baseline: String = {
28 | "abc"
29 | }
30 |
31 | @Benchmark
32 | def sInterpolator: String = {
33 | s"abc"
34 | }
35 |
36 | @Benchmark
37 | def sfiInterpolator: String = {
38 | import com.komanov.stringformat.macros.MacroConcat._
39 | sfi"abc"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/scala-string-format-test/src/main/scala/com/komanov/stringformat/jmh/StringBuilderBenchmark.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat.jmh
2 |
3 | import org.openjdk.jmh.annotations.Benchmark
4 |
5 | class StringBuilderBenchmark extends BenchmarkBase {
6 |
7 | @Benchmark
8 | def javaStringBuilder: String = {
9 | new java.lang.StringBuilder()
10 | .append("abc")
11 | .append("def")
12 | .toString
13 | }
14 |
15 | @Benchmark
16 | def javaStringBuilder2: String = {
17 | new java.lang.StringBuilder()
18 | .append("string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495")
19 | .append("string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495")
20 | .toString
21 | }
22 |
23 | @Benchmark
24 | def scalaStringBuilder: String = {
25 | new scala.collection.mutable.StringBuilder()
26 | .append("abc")
27 | .append("def")
28 | .toString
29 | }
30 |
31 | @Benchmark
32 | def scalaStringBuilder2: String = {
33 | new scala.collection.mutable.StringBuilder()
34 | .append("string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495")
35 | .append("string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495")
36 | .toString
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/scala-string-format/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.komanov
8 | scala-string-format
9 | 1.0-SNAPSHOT
10 |
11 |
12 | scala-string-format-all
13 | com.komanov
14 | 1.0-SNAPSHOT
15 |
16 |
17 |
18 |
19 | org.scala-lang
20 | scala-library
21 | 2.12.0
22 |
23 |
24 | org.scala-lang
25 | scala-reflect
26 | 2.12.0
27 |
28 |
29 |
30 |
31 |
32 | org.specs2
33 | specs2-core_2.12
34 | 3.8.6
35 | test
36 |
37 |
38 | org.specs2
39 | specs2-matcher-extra_2.12
40 | 3.8.6
41 | test
42 |
43 |
44 | org.specs2
45 | specs2-mock_2.12
46 | 3.8.6
47 | test
48 |
49 |
50 | org.specs2
51 | specs2-junit_2.12
52 | 3.8.6
53 | test
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/scala-string-format/src/main/scala/com/komanov/stringformat/FastStringFactory.java:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodHandles;
5 | import java.lang.reflect.Constructor;
6 | import java.lang.reflect.Field;
7 |
8 | public class FastStringFactory {
9 |
10 | private static final MethodHandle stringBuilderValueGetter = getValueHandler();
11 | private static final MethodHandle newString = getNewStringHandler();
12 |
13 | public static String fastNewString(StringBuilder sb) throws Throwable {
14 | if (sb.capacity() != sb.length()) {
15 | throw new IllegalArgumentException("Expected filled StringBuilder!");
16 | }
17 |
18 | return fastNewString(getValue(sb));
19 | }
20 |
21 | public static char[] getValue(StringBuilder sb) throws Throwable {
22 | return (char[]) stringBuilderValueGetter.invoke(sb);
23 | }
24 |
25 | public static String fastNewString(char[] chars) throws Throwable {
26 | return (String) newString.invokeExact(chars, true);
27 | }
28 |
29 | private static MethodHandle getValueHandler() {
30 | try {
31 | Field field = getValueField();
32 | return MethodHandles.lookup().unreflectGetter(field);
33 | }
34 | catch (Exception e) {
35 | throw new RuntimeException(e);
36 | }
37 | }
38 |
39 | private static Field getValueField() throws NoSuchFieldException {
40 | Field field = StringBuilder.class.getSuperclass().getDeclaredField("value");
41 | field.setAccessible(true);
42 | return field;
43 | }
44 |
45 | private static MethodHandle getNewStringHandler() {
46 | try {
47 | Constructor constructor = String.class.getDeclaredConstructor(char[].class, boolean.class);
48 | constructor.setAccessible(true);
49 | return MethodHandles.lookup().unreflectConstructor(constructor);
50 | }
51 | catch (Exception e) {
52 | throw new RuntimeException(e);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/scala-string-format/src/main/scala/com/komanov/stringformat/OptimizedConcatenation1.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | import scala.annotation.varargs
4 |
5 | object OptimizedConcatenation1 {
6 |
7 | type StringBuilder = java.lang.StringBuilder
8 |
9 | def concat(o1: Object, o2: Object): String = {
10 | concatNonNull(orNull(o1), orNull(o2))
11 | }
12 |
13 | def concat(o1: Object, o2: Object, o3: Object): String = {
14 | concatNonNull(orNull(o1), orNull(o2), orNull(o3))
15 | }
16 |
17 | def concat(o1: Object, o2: Object, o3: Object, o4: Object): String = {
18 | concatNonNull(orNull(o1), orNull(o2), orNull(o3), orNull(o4))
19 | }
20 |
21 | def concat(o1: Object, o2: Object, o3: Object, o4: Object, o5: Object): String = {
22 | concatNonNull(orNull(o1), orNull(o2), orNull(o3), orNull(o4), orNull(o5))
23 | }
24 |
25 | def concat(o1: Object, o2: Object, o3: Object, o4: Object, o5: Object, o6: Object): String = {
26 | concatNonNull(orNull(o1), orNull(o2), orNull(o3), orNull(o4), orNull(o5), orNull(o6))
27 | }
28 |
29 | @varargs
30 | def concatObjects(objects: Object*): String = {
31 | if (objects.isEmpty) {
32 | ""
33 | } else {
34 | val sb = new StringBuilder(objects.size * 8)
35 | objects.foldLeft(sb)((sb, s) => if (s != null) sb.append(s) else sb).toString
36 | }
37 | }
38 |
39 | def concat(s1: String, s2: String): String = {
40 | concatNonNull(orNull(s1), orNull(s2))
41 | }
42 |
43 | def concat(s1: String, s2: String, s3: String): String = {
44 | concatNonNull(orNull(s1), orNull(s2), orNull(s3))
45 | }
46 |
47 | def concat(s1: String, s2: String, s3: String, s4: String): String = {
48 | concatNonNull(orNull(s1), orNull(s2), orNull(s3), orNull(s4))
49 | }
50 |
51 | def concat(s1: String, s2: String, s3: String, s4: String, s5: String): String = {
52 | concatNonNull(orNull(s1), orNull(s2), orNull(s3), orNull(s4), orNull(s5))
53 | }
54 |
55 | def concat(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String): String = {
56 | concatNonNull(orNull(s1), orNull(s2), orNull(s3), orNull(s4), orNull(s5), orNull(s6))
57 | }
58 |
59 | @varargs
60 | def concatStrings(strings: String*): String = {
61 | if (strings.isEmpty) {
62 | ""
63 | } else {
64 | val len = strings.map(s => if (s == null) 0 else s.length).sum
65 | val sb = new StringBuilder(len)
66 | strings.foldLeft(sb)((sb, s) => if (s != null) sb.append(s) else sb).toString
67 | }
68 | }
69 |
70 | private def concatNonNull(s1: String, s2: String): String = {
71 | new StringBuilder(s1.length + s2.length)
72 | .append(s1)
73 | .append(s2)
74 | .toString
75 | }
76 |
77 | private def concatNonNull(s1: String, s2: String, s3: String): String = {
78 | new StringBuilder(s1.length + s2.length + s3.length)
79 | .append(s1)
80 | .append(s2)
81 | .append(s3)
82 | .toString
83 | }
84 |
85 | private def concatNonNull(s1: String, s2: String, s3: String, s4: String): String = {
86 | new StringBuilder(s1.length + s2.length + s3.length + s4.length)
87 | .append(s1)
88 | .append(s2)
89 | .append(s3)
90 | .append(s4)
91 | .toString
92 | }
93 |
94 | private def concatNonNull(s1: String, s2: String, s3: String, s4: String, s5: String): String = {
95 | new StringBuilder(s1.length + s2.length + s3.length + s4.length + s5.length)
96 | .append(s1)
97 | .append(s2)
98 | .append(s3)
99 | .append(s4)
100 | .append(s5)
101 | .toString
102 | }
103 |
104 | private def concatNonNull(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String): String = {
105 | new StringBuilder(s1.length + s2.length + s3.length + s4.length + s5.length + s6.length)
106 | .append(s1)
107 | .append(s2)
108 | .append(s3)
109 | .append(s4)
110 | .append(s5)
111 | .append(s6)
112 | .toString
113 | }
114 |
115 | @inline
116 | private def orNull(o: Object): String = if (o == null) "null" else o.toString
117 |
118 | @inline
119 | private def orNull(s: String): String = if (s == null) "null" else s
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/scala-string-format/src/main/scala/com/komanov/stringformat/OptimizedConcatenation2.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | import scala.annotation.varargs
4 |
5 | object OptimizedConcatenation2 {
6 |
7 | type StringBuilder = java.lang.StringBuilder
8 |
9 | def concat(o1: Object, o2: Object): String = {
10 | concatNonNull(orNull(o1), orNull(o2))
11 | }
12 |
13 | def concat(o1: Object, o2: Object, o3: Object): String = {
14 | concatNonNull(orNull(o1), orNull(o2), orNull(o3))
15 | }
16 |
17 | def concat(o1: Object, o2: Object, o3: Object, o4: Object): String = {
18 | concatNonNull(orNull(o1), orNull(o2), orNull(o3), orNull(o4))
19 | }
20 |
21 | def concat(o1: Object, o2: Object, o3: Object, o4: Object, o5: Object): String = {
22 | concatNonNull(orNull(o1), orNull(o2), orNull(o3), orNull(o4), orNull(o5))
23 | }
24 |
25 | def concat(o1: Object, o2: Object, o3: Object, o4: Object, o5: Object, o6: Object): String = {
26 | concatNonNull(orNull(o1), orNull(o2), orNull(o3), orNull(o4), orNull(o5), orNull(o6))
27 | }
28 |
29 | @varargs
30 | def concatObjects(objects: Object*): String = {
31 | if (objects.isEmpty) {
32 | ""
33 | } else {
34 | val sb = new StringBuilder(objects.size * 8)
35 | objects.foldLeft(sb)((sb, s) => if (s != null) sb.append(s) else sb).toString
36 | }
37 | }
38 |
39 | def concat(s1: String, s2: String): String = {
40 | concatNonNull(orNull(s1), orNull(s2))
41 | }
42 |
43 | def concat(s1: String, s2: String, s3: String): String = {
44 | concatNonNull(orNull(s1), orNull(s2), orNull(s3))
45 | }
46 |
47 | def concat(s1: String, s2: String, s3: String, s4: String): String = {
48 | concatNonNull(orNull(s1), orNull(s2), orNull(s3), orNull(s4))
49 | }
50 |
51 | def concat(s1: String, s2: String, s3: String, s4: String, s5: String): String = {
52 | concatNonNull(orNull(s1), orNull(s2), orNull(s3), orNull(s4), orNull(s5))
53 | }
54 |
55 | def concat(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String): String = {
56 | concatNonNull(orNull(s1), orNull(s2), orNull(s3), orNull(s4), orNull(s5), orNull(s6))
57 | }
58 |
59 | @varargs
60 | def concatStrings(strings: String*): String = {
61 | if (strings.isEmpty) {
62 | ""
63 | } else {
64 | val len = strings.map(s => if (s == null) 0 else s.length).sum
65 | val sb = new StringBuilder(len)
66 | strings.foldLeft(sb)((sb, s) => if (s != null) sb.append(s) else sb).toString
67 | }
68 | }
69 |
70 | private def concatNonNull(s1: String, s2: String): String = {
71 | val sb = new StringBuilder(s1.length + s2.length)
72 | .append(s1)
73 | .append(s2)
74 | FastStringFactory.fastNewString(sb)
75 | }
76 |
77 | private def concatNonNull(s1: String, s2: String, s3: String): String = {
78 | val sb = new StringBuilder(s1.length + s2.length + s3.length)
79 | .append(s1)
80 | .append(s2)
81 | .append(s3)
82 |
83 | FastStringFactory.fastNewString(sb)
84 | }
85 |
86 | private def concatNonNull(s1: String, s2: String, s3: String, s4: String): String = {
87 | val sb = new StringBuilder(s1.length + s2.length + s3.length + s4.length)
88 | .append(s1)
89 | .append(s2)
90 | .append(s3)
91 | .append(s4)
92 | FastStringFactory.fastNewString(sb)
93 | }
94 |
95 | private def concatNonNull(s1: String, s2: String, s3: String, s4: String, s5: String): String = {
96 | val sb = new StringBuilder(s1.length + s2.length + s3.length + s4.length + s5.length)
97 | .append(s1)
98 | .append(s2)
99 | .append(s3)
100 | .append(s4)
101 | .append(s5)
102 | FastStringFactory.fastNewString(sb)
103 | }
104 |
105 | private def concatNonNull(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String): String = {
106 | val sb = new StringBuilder(s1.length + s2.length + s3.length + s4.length + s5.length + s6.length)
107 | .append(s1)
108 | .append(s2)
109 | .append(s3)
110 | .append(s4)
111 | .append(s5)
112 | .append(s6)
113 | FastStringFactory.fastNewString(sb)
114 | }
115 |
116 | @inline
117 | private def orNull(o: Object): String = if (o == null) "null" else o.toString
118 |
119 | @inline
120 | private def orNull(s: String): String = if (s == null) "null" else s
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/scala-string-format/src/main/scala/com/komanov/stringformat/macros/MacroConcat.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat.macros
2 |
3 | import scala.language.experimental.macros
4 | import scala.reflect.macros.whitebox
5 |
6 | object MacroConcat {
7 |
8 | implicit class SuperFastInterpolator(sc: StringContext) {
9 | def so(args: Any*): String = macro soImpl
10 |
11 | def sfi(args: Any*): String = macro sfiImpl
12 | }
13 |
14 | def soImpl(c: whitebox.Context)(args: c.Expr[Any]*): c.Expr[String] = {
15 | import c.universe._
16 |
17 | val constantParts = extractConstantPartsAndTreatEscapes(c)
18 |
19 | if (args.isEmpty) {
20 | return c.Expr(Literal(Constant(constantParts.mkString(""))))
21 | }
22 |
23 | val initialLength = constantParts.map(_.length).sum
24 |
25 | val (valDeclarations, lenNames, arguments) = args.zipWithIndex.map {
26 | case (e, index) =>
27 | val name = TermName("__local" + index)
28 | e.actualType match {
29 | case tt if tt.typeSymbol.isClass && tt.typeSymbol.asClass.isPrimitive =>
30 | val expr = q"val $name = $e.toString"
31 | (List(expr), List(name), Ident(name))
32 | case _ =>
33 | val expr =
34 | q"""
35 | val $name = {
36 | val tmp = $e
37 | if (tmp == null) "null" else tmp.toString
38 | }"""
39 | (List(expr), List(name), Ident(name))
40 | }
41 | }.unzip3
42 |
43 | val allParts = getAllPartsForAppend(c)(constantParts, arguments)
44 |
45 | // code generation
46 |
47 | val plusLenExpr = lengthSum(c)(initialLength, lenNames.flatten.toList)
48 |
49 | val newStringBuilderAndAppendExpr = newStringBuilderWithAppends(c)(q"new java.lang.StringBuilder($plusLenExpr)", allParts.reverse)
50 |
51 | val stats = valDeclarations.flatten.toList :+
52 | q"$newStringBuilderAndAppendExpr.toString"
53 |
54 | c.Expr(
55 | q"..$stats"
56 | )
57 | }
58 |
59 | def sfiImpl(c: whitebox.Context)(args: c.Expr[Any]*): c.Expr[String] = {
60 | import c.universe._
61 |
62 | val constantParts = extractConstantPartsAndTreatEscapes(c)
63 |
64 | if (args.isEmpty) {
65 | return c.Expr(Literal(Constant(constantParts.mkString(""))))
66 | }
67 |
68 | var initialLength = constantParts.map(_.length).sum
69 |
70 | val (valDeclarations, lenNames, arguments: Seq[c.universe.Tree]) = args.zipWithIndex.map {
71 | case (e, index) =>
72 | val name = TermName("__local" + index)
73 | e.actualType match {
74 | case tt if tt.typeSymbol.isClass && tt.typeSymbol.asClass.isPrimitive =>
75 | // A kind of optimization to not calculate primitive length in advance, let the StringBuilder
76 | // to deal with primitive toString (it's better than i.e. Int.toString static method).
77 | initialLength += 9
78 | (Nil, Nil, e.tree)
79 | case tt if tt <:< typeOf[CharSequence] =>
80 | val expr =
81 | q"""
82 | val $name = {
83 | val tmp = $e
84 | if (tmp eq null) "null" else tmp
85 | }"""
86 | (List(expr), List(name), Ident(name))
87 | case _ =>
88 | val expr =
89 | q"""
90 | val $name = {
91 | val tmp = $e
92 | if (tmp == null) "null" else tmp.toString
93 | }"""
94 | (List(expr), List(name), Ident(name))
95 | }
96 | }.unzip3
97 |
98 | val allParts = getAllPartsForAppend(c)(constantParts, arguments)
99 |
100 | // code generation
101 |
102 | val plusLenExpr = lengthSum(c)(initialLength, lenNames.flatten.toList)
103 |
104 | val stringBuilderWithAppends = newStringBuilderWithAppends(c)(q"new java.lang.StringBuilder(len)", allParts.reverse)
105 |
106 | val stats = valDeclarations.flatten.toList ++
107 | List(q"val len = $plusLenExpr") :+
108 | q"$stringBuilderWithAppends.toString"
109 |
110 | c.Expr(
111 | q"..$stats"
112 | )
113 | }
114 |
115 | private def getAllPartsForAppend(c: whitebox.Context)(rawParts: Seq[String], arguments: Seq[c.universe.Tree]) = {
116 | import c.universe._
117 |
118 | def nilOrConst(s: String) = {
119 | if (s.isEmpty) Nil else List(Literal(Constant(s)))
120 | }
121 |
122 | rawParts.zipAll(arguments, null, null).flatMap {
123 | case (raw, null) => nilOrConst(raw)
124 | case (raw, arg) if raw != null => nilOrConst(raw) :+ arg
125 | }.toList
126 | }
127 |
128 | private def extractConstantPartsAndTreatEscapes(c: whitebox.Context): List[String] = {
129 | import c.universe._
130 |
131 | val rawParts = c.prefix.tree match {
132 | case Apply(_, List(Apply(_, list))) => list
133 | }
134 |
135 | rawParts.map {
136 | case Literal(Constant(rawPart: String)) => StringContext.treatEscapes(rawPart)
137 | }
138 | }
139 |
140 | private def newStringBuilderWithAppends(c: whitebox.Context)
141 | (newStringBuilderExpr: c.universe.Tree,
142 | expressions: List[c.universe.Tree]): c.universe.Tree = {
143 | import c.universe._
144 |
145 | if (expressions.isEmpty) {
146 | newStringBuilderExpr
147 | } else {
148 | Apply(Select(newStringBuilderWithAppends(c)(newStringBuilderExpr, expressions.tail), TermName("append")), List(expressions.head))
149 | }
150 | }
151 |
152 | private def lengthSum(c: whitebox.Context)(const: Int, names: List[c.universe.TermName]): c.universe.Tree = {
153 | import c.universe._
154 |
155 | if (names.isEmpty) {
156 | Literal(Constant(const))
157 | } else {
158 | Apply(Select(Select(Ident(names.head), TermName("length")), TermName("$plus")), List(lengthSum(c)(const, names.tail)))
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/scala-string-format/src/test/scala/com/komanov/stringformat/OptimizedConcatenation1Test.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | import org.specs2.mutable.SpecificationWithJUnit
4 |
5 | class OptimizedConcatenation1Test extends SpecificationWithJUnit {
6 |
7 | "concat" should {
8 | "objects" >> {
9 | OptimizedConcatenation1.concat(o1, o2) must be_===(s1 + s2)
10 | OptimizedConcatenation1.concat(o1, o2, o3) must be_===(s1 + s2 + s3)
11 | OptimizedConcatenation1.concat(o1, o2, o3, o4) must be_===(s1 + s2 + s3 + s4)
12 | OptimizedConcatenation1.concat(o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5)
13 | OptimizedConcatenation1.concat(o1, o2, o3, o4, o5, o6) must be_===(s1 + s2 + s3 + s4 + s5 + s6)
14 | OptimizedConcatenation1.concatObjects(o1, o2, o3, o4, o5, o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5)
15 | }
16 |
17 | "strings" >> {
18 | OptimizedConcatenation1.concat(s1, s2) must be_===(s1 + s2)
19 | OptimizedConcatenation1.concat(s1, s2, s3) must be_===(s1 + s2 + s3)
20 | OptimizedConcatenation1.concat(s1, s2, s3, s4) must be_===(s1 + s2 + s3 + s4)
21 | OptimizedConcatenation1.concat(s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5)
22 | OptimizedConcatenation1.concat(s1, s2, s3, s4, s5, s6) must be_===(s1 + s2 + s3 + s4 + s5 + s6)
23 | OptimizedConcatenation1.concatStrings(s1, s2, s3, s4, s5, s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5)
24 | }
25 |
26 | "replace null with 'null' string" >> {
27 | OptimizedConcatenation1.concat(null, Int.box(1)) must be_===("null1")
28 | OptimizedConcatenation1.concat(null, "1") must be_===("null1")
29 | }
30 | }
31 |
32 | val o1: Object = Int.box(10000)
33 | val o2: Object = Int.box(200000)
34 | val o3: Object = Int.box(3000000)
35 | val o4: Object = Int.box(40000000)
36 | val o5: Object = Int.box(500000000)
37 | val o6: Object = Int.box(60000000)
38 | val o7: Object = Int.box(7000000)
39 |
40 | val s1 = "10000"
41 | val s2 = "200000"
42 | val s3 = "3000000"
43 | val s4 = "40000000"
44 | val s5 = "500000000"
45 | val s6 = "60000000"
46 | val s7 = "7000000"
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/scala-string-format/src/test/scala/com/komanov/stringformat/OptimizedConcatenation2Test.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat
2 |
3 | import org.specs2.mutable.SpecificationWithJUnit
4 |
5 | class OptimizedConcatenation2Test extends SpecificationWithJUnit {
6 |
7 | "concat" should {
8 | "objects" >> {
9 | OptimizedConcatenation2.concat(o1, o2) must be_===(s1 + s2)
10 | OptimizedConcatenation2.concat(o1, o2, o3) must be_===(s1 + s2 + s3)
11 | OptimizedConcatenation2.concat(o1, o2, o3, o4) must be_===(s1 + s2 + s3 + s4)
12 | OptimizedConcatenation2.concat(o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5)
13 | OptimizedConcatenation2.concat(o1, o2, o3, o4, o5, o6) must be_===(s1 + s2 + s3 + s4 + s5 + s6)
14 | OptimizedConcatenation2.concatObjects(o1, o2, o3, o4, o5, o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5)
15 | }
16 |
17 | "strings" >> {
18 | OptimizedConcatenation2.concat(s1, s2) must be_===(s1 + s2)
19 | OptimizedConcatenation2.concat(s1, s2, s3) must be_===(s1 + s2 + s3)
20 | OptimizedConcatenation2.concat(s1, s2, s3, s4) must be_===(s1 + s2 + s3 + s4)
21 | OptimizedConcatenation2.concat(s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5)
22 | OptimizedConcatenation2.concat(s1, s2, s3, s4, s5, s6) must be_===(s1 + s2 + s3 + s4 + s5 + s6)
23 | OptimizedConcatenation2.concatStrings(s1, s2, s3, s4, s5, s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5)
24 | }
25 |
26 | "replace null with 'null' string" >> {
27 | OptimizedConcatenation2.concat(null, Int.box(1)) must be_===("null1")
28 | OptimizedConcatenation2.concat(null, "1") must be_===("null1")
29 | }
30 | }
31 |
32 | val o1: Object = Int.box(10000)
33 | val o2: Object = Int.box(200000)
34 | val o3: Object = Int.box(3000000)
35 | val o4: Object = Int.box(40000000)
36 | val o5: Object = Int.box(500000000)
37 | val o6: Object = Int.box(60000000)
38 | val o7: Object = Int.box(7000000)
39 |
40 | val s1 = "10000"
41 | val s2 = "200000"
42 | val s3 = "3000000"
43 | val s4 = "40000000"
44 | val s5 = "500000000"
45 | val s6 = "60000000"
46 | val s7 = "7000000"
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/scala-string-format/src/test/scala/com/komanov/stringformat/macros/MacrosConcatTest.scala:
--------------------------------------------------------------------------------
1 | package com.komanov.stringformat.macros
2 |
3 | import com.komanov.stringformat.macros.MacroConcat._
4 | import org.specs2.mock.Mockito
5 | import org.specs2.mutable.SpecificationWithJUnit
6 |
7 | class MacrosConcatTest extends SpecificationWithJUnit with Mockito {
8 |
9 | "Super Fast Interpolation" should {
10 | "work as s" >> {
11 | sfi"" must be_===(s"")
12 | sfi"abc" must be_===(s"abc")
13 | sfi"$o1" must be_===(s"$o1")
14 | sfi"${o1}after" must be_===(s"${o1}after")
15 | sfi"before${o1}" must be_===(s"before${o1}")
16 | sfi"before${o1}after" must be_===(s"before${o1}after")
17 | sfi"$o1$o2" must be_===(s"$o1$o2")
18 | sfi"!$o1!$o2!" must be_===(s"!$o1!$o2!")
19 | sfi"!$s1!$s2!$s3!$s4!$s5!$s6!$s7!$o1!$o2!$o3!$o4!$o5!$o6!$o7!" must be_===(s"!$s1!$s2!$s3!$s4!$s5!$s6!$s7!$o1!$o2!$o3!$o4!$o5!$o6!$o7!")
20 | }
21 |
22 | "serialize null as s" >> {
23 | sfi"$nullObject" must be_===(s"$nullObject")
24 | }
25 |
26 | "support expressions" >> {
27 | sfi"${car.name}" must be_===(s"${car.name}")
28 | sfi"${if (true) "1" else "0"}" must be_===(s"${if (true) "1" else "0"}")
29 | }
30 |
31 | "support parametric types" >> {
32 | testSfi(1) must be_===(testS(1))
33 | testSfi('A') must be_===(testS('A'))
34 | testSfi("A") must be_===(testS("A"))
35 | }
36 |
37 | def testSfi[A](a: A): String = sfi"$a"
38 |
39 | def testS[A](a: A): String = s"$a"
40 |
41 | "don't call expression multiple times" >> {
42 | val m = mock[Something]
43 |
44 | m.id returns 1
45 | m.name returns "name"
46 | m.obj returns null
47 |
48 | sfi"${m.id} - ${m.name} - ${m.obj} - ${m.id}" must be_===("1 - name - null - 1")
49 |
50 | got {
51 | two(m).id
52 | one(m).name
53 | one(m).obj
54 | noMoreCallsTo(m)
55 | }
56 | }
57 | }
58 |
59 | "Optimized Concatenation Interpolation" should {
60 | "work as s" >> {
61 | so"" must be_===(s"")
62 | so"abc" must be_===(s"abc")
63 | so"$o1" must be_===(s"$o1")
64 | so"${o1}after" must be_===(s"${o1}after")
65 | so"before${o1}" must be_===(s"before${o1}")
66 | so"before${o1}after" must be_===(s"before${o1}after")
67 | so"$o1$o2" must be_===(s"$o1$o2")
68 | so"!$o1!$o2!" must be_===(s"!$o1!$o2!")
69 | so"!$s1!$s2!$s3!$s4!$s5!$s6!$s7!$o1!$o2!$o3!$o4!$o5!$o6!$o7!" must be_===(s"!$s1!$s2!$s3!$s4!$s5!$s6!$s7!$o1!$o2!$o3!$o4!$o5!$o6!$o7!")
70 | }
71 |
72 | "serialize null as s" >> {
73 | so"$nullObject" must be_===(s"$nullObject")
74 | }
75 |
76 | "support expressions" >> {
77 | so"${car.name}" must be_===(s"${car.name}")
78 | }
79 |
80 | "don't call expression multiple times" >> {
81 | val m = mock[Something]
82 |
83 | m.id returns 1
84 | m.name returns "name"
85 | m.obj returns null
86 |
87 | so"${m.id} - ${m.name} - ${m.obj} - ${m.id}" must be_===("1 - name - null - 1")
88 |
89 | got {
90 | two(m).id
91 | one(m).name
92 | one(m).obj
93 | noMoreCallsTo(m)
94 | }
95 | }
96 | }
97 |
98 | val o1: Object = Int.box(10000)
99 | val o2: Object = Int.box(200000)
100 | val o3: Object = Int.box(3000000)
101 | val o4: Object = Int.box(40000000)
102 | val o5: Object = Int.box(500000000)
103 | val o6: Object = Int.box(60000000)
104 | val o7: Object = Int.box(7000000)
105 | val nullObject: Object = null
106 |
107 | val s1 = "10000"
108 | val s2 = "200000"
109 | val s3 = "3000000"
110 | val s4 = "40000000"
111 | val s5 = "500000000"
112 | val s6 = "60000000"
113 | val s7 = "7000000"
114 |
115 | val car = Car("f1")
116 |
117 | trait Something {
118 | def name: String
119 |
120 | def id: Int
121 |
122 | def obj: Object
123 | }
124 |
125 | case class Car(name: String)
126 |
127 | }
128 |
--------------------------------------------------------------------------------