├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── test └── java │ └── net │ └── codestory │ └── FluentTest.java └── main └── java └── net └── codestory └── Fluent.java /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use docker-based build environment 2 | sudo: false 3 | 4 | language: java 5 | jdk: 6 | - oraclejdk8 7 | 8 | cache: 9 | directories: 10 | - '$HOME/.m2/repository' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 all@code-story.net 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fluent 2 | 3 | Java 8 Streams and Iterables on Steroids. 4 | 5 | # Build status 6 | 7 | [![Build Status](https://api.travis-ci.org/CodeStory/fluent.png)](https://travis-ci.org/CodeStory/fluent) 8 | 9 | # Environment 10 | 11 | - `java-1.8` 12 | 13 | ## Maven 14 | 15 | Release versions are deployed on Maven Central: 16 | 17 | ```xml 18 | 19 | net.code-story 20 | fluent 21 | 1.4 22 | 23 | ``` 24 | 25 | # Build 26 | 27 | ```bash 28 | mvn clean verify 29 | ``` 30 | 31 | # Deploy on Maven Central 32 | 33 | Build the release: 34 | 35 | ```bash 36 | mvn release:clean release:prepare release:perform 37 | ``` 38 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | net.code-story 6 | fluent 7 | 1.5-SNAPSHOT 8 | jar 9 | 10 | CodeStory - Fluent Stream/Iterable 11 | Streams and Iterables on Steroids 12 | https://github.com/CodeStory/fluent 13 | 14 | 15 | org.sonatype.oss 16 | oss-parent 17 | 9 18 | 19 | 20 | 21 | 22 | scm:git:git@github.com:CodeStory/fluent.git 23 | scm:git:git@github.com:CodeStory/fluent.git 24 | scm:git:git@github.com:CodeStory/fluent.git 25 | HEAD 26 | 27 | 28 | 29 | 30 | Apache 2 31 | http://www.apache.org/licenses/LICENSE-2.0.txt 32 | repo 33 | A business-friendly OSS license 34 | 35 | 36 | 37 | 38 | 3.0.4 39 | 40 | 41 | 42 | UTF-8 43 | 1.8 44 | 1.8 45 | 46 | 47 | 48 | 49 | release 50 | 51 | 52 | 53 | maven-gpg-plugin 54 | 1.5 55 | 56 | 57 | sign-artifacts 58 | verify 59 | 60 | sign 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | maven-clean-plugin 75 | 2.5 76 | 77 | 78 | maven-compiler-plugin 79 | 3.1 80 | 81 | 82 | maven-deploy-plugin 83 | 2.8.1 84 | 85 | 86 | maven-install-plugin 87 | 2.5.1 88 | 89 | 90 | maven-jar-plugin 91 | 2.5 92 | 93 | 94 | maven-resources-plugin 95 | 2.6 96 | 97 | 98 | maven-site-plugin 99 | 3.4 100 | 101 | 102 | maven-source-plugin 103 | 2.3 104 | 105 | 106 | maven-release-plugin 107 | 2.5 108 | 109 | 110 | maven-surefire-plugin 111 | 2.17 112 | 113 | 114 | 115 | 116 | 117 | org.sonatype.plugins 118 | nexus-staging-maven-plugin 119 | 1.6.2 120 | true 121 | 122 | ossrh 123 | https://oss.sonatype.org/ 124 | true 125 | 126 | 127 | 128 | maven-release-plugin 129 | 130 | release 131 | 132 | 133 | 134 | maven-source-plugin 135 | 136 | 137 | attach-sources 138 | 139 | jar 140 | 141 | 142 | 143 | 144 | 145 | false 146 | com.mycila.maven-license-plugin 147 | maven-license-plugin 148 | 1.9.0 149 | 150 |
${project.basedir}/LICENSE
151 | true 152 | true 153 | true 154 | 155 | **/*.java 156 | 157 | 158 | JAVADOC_STYLE 159 | 160 |
161 | 162 | 163 | enforce-license-headers 164 | validate 165 | 166 | check 167 | 168 | 169 | 170 |
171 |
172 |
173 | 174 | 175 | 176 | 177 | junit 178 | junit 179 | 4.12-beta-2 180 | test 181 | 182 | 183 | org.assertj 184 | assertj-core 185 | 1.7.0 186 | test 187 | 188 | 189 |
190 | -------------------------------------------------------------------------------- /src/test/java/net/codestory/FluentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 all@code-story.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | package net.codestory; 17 | 18 | import org.junit.Test; 19 | 20 | import java.util.*; 21 | import java.util.stream.*; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | public class FluentTest { 26 | @Test 27 | public void create_empty() { 28 | Iterable empty = Fluent.of(); 29 | 30 | assertThat(empty).isEmpty(); 31 | } 32 | 33 | @Test 34 | public void create_for_array() { 35 | Iterable values = Fluent.of("FIRST", "SECOND"); 36 | 37 | assertThat(values).containsExactly("FIRST", "SECOND"); 38 | } 39 | 40 | @Test 41 | public void create_for_iterable() { 42 | Iterable values = Fluent.of(Arrays.asList("FIRST", "SECOND")); 43 | 44 | assertThat(values).containsExactly("FIRST", "SECOND"); 45 | } 46 | 47 | @Test 48 | public void create_for_self() { 49 | Fluent fluent = Fluent.of(Arrays.asList("FIRST", "SECOND")); 50 | Iterable values = Fluent.of(fluent); 51 | 52 | assertThat((Object) values).isSameAs(fluent); 53 | } 54 | 55 | @Test 56 | public void create_for_iterator() { 57 | Fluent fluent = Fluent.of(Arrays.asList("FIRST", "SECOND").iterator()); 58 | 59 | assertThat(fluent.toList()).containsExactly("FIRST", "SECOND"); 60 | assertThat(fluent.toList()).isEmpty(); 61 | } 62 | 63 | @Test 64 | public void create_for_stream() { 65 | Fluent fluent = Fluent.of(Stream.of("FIRST", "SECOND")); 66 | 67 | assertThat(fluent.toList()).containsExactly("FIRST", "SECOND"); 68 | } 69 | 70 | @Test 71 | public void create_for_int_array() { 72 | int[] ints = {1, 2, 3, 4, 5}; 73 | 74 | Iterable values = Fluent.of(ints); 75 | 76 | assertThat(values).containsExactly(1, 2, 3, 4, 5); 77 | } 78 | 79 | @Test 80 | public void create_for_long_array() { 81 | long[] longs = {1L, 2L, 3L, 4L, 5L}; 82 | 83 | Iterable values = Fluent.of(longs); 84 | 85 | assertThat(values).containsExactly(1L, 2L, 3L, 4L, 5L); 86 | } 87 | 88 | @Test 89 | public void create_for_double_array() { 90 | double[] doubles = {1, 2, 3, 4, 5}; 91 | 92 | Iterable values = Fluent.of(doubles); 93 | 94 | assertThat(values).containsExactly(1D, 2D, 3D, 4D, 5D); 95 | } 96 | 97 | @Test 98 | public void transform_values() { 99 | Iterable values = Fluent.of("a", "b").map(String::toUpperCase); 100 | 101 | assertThat(values).containsExactly("A", "B"); 102 | } 103 | 104 | @Test 105 | public void filter_values() { 106 | Iterable values = Fluent.of(1, 2, 3, 4, 5).filter(v -> v > 3); 107 | 108 | assertThat(values).containsExactly(4, 5); 109 | } 110 | 111 | @Test 112 | public void exclude_values() { 113 | Iterable values = Fluent.of(1, 2, 3, 4, 5).exclude(v -> v > 3); 114 | 115 | assertThat(values).containsExactly(1, 2, 3); 116 | } 117 | 118 | @Test 119 | public void count_values() { 120 | long count = Fluent.of(1, 2, 3, 4, 5).size(); 121 | 122 | assertThat(count).isEqualTo(5L); 123 | } 124 | 125 | @Test 126 | public void count_with_predicate() { 127 | long count = Fluent.of(1, 2, 3, 4, 5).count(v -> v > 3); 128 | 129 | assertThat(count).isEqualTo(2L); 130 | } 131 | 132 | @Test 133 | public void first_value() { 134 | Optional first = Fluent.of(1, 2, 3, 4, 5).first(); 135 | 136 | assertThat(first.get()).isEqualTo(1); 137 | } 138 | 139 | @Test 140 | public void no_first_value() { 141 | Optional first = Fluent.of().first(); 142 | 143 | assertThat(first.isPresent()).isFalse(); 144 | } 145 | 146 | @Test(expected = NullPointerException.class) 147 | public void first_null_value() { 148 | Fluent.of((Object) null).first(); 149 | } 150 | 151 | @Test 152 | public void last_value() { 153 | Optional last = Fluent.of(1, 2, 3, 4, 5).last(); 154 | 155 | assertThat(last.get()).isEqualTo(5); 156 | } 157 | 158 | @Test 159 | public void no_last_value() { 160 | Optional last = Fluent.of().last(); 161 | 162 | assertThat(last.isPresent()).isFalse(); 163 | } 164 | 165 | @Test(expected = NullPointerException.class) 166 | public void last_null_value() { 167 | Fluent.of((Object) null).last(); 168 | } 169 | 170 | @Test 171 | public void empty() { 172 | assertThat(Fluent.of().isEmpty()).isTrue(); 173 | assertThat(Fluent.of("VALUE").isEmpty()).isFalse(); 174 | } 175 | 176 | @Test 177 | public void contains() { 178 | Fluent values = Fluent.of(1, 2); 179 | 180 | assertThat(values.contains(1)).isTrue(); 181 | assertThat(values.contains(2)).isTrue(); 182 | assertThat(values.contains(3)).isFalse(); 183 | assertThat(values.contains(null)).isFalse(); 184 | assertThat(values.contains("DIFFERENT TYPE")).isFalse(); 185 | } 186 | 187 | @Test 188 | public void index() { 189 | Fluent values = Fluent.of(1, 2); 190 | 191 | assertThat(values.indexOf(1)).isEqualTo(0); 192 | assertThat(values.indexOf(2)).isEqualTo(1); 193 | assertThat(values.indexOf(3)).isEqualTo(-1); 194 | assertThat(values.indexOf(null)).isEqualTo(-1); 195 | assertThat(values.indexOf("DIFFERENT TYPE")).isEqualTo(-1); 196 | } 197 | 198 | @Test 199 | public void to_array() { 200 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 201 | 202 | Integer[] array = values.toArray(Integer[]::new); 203 | 204 | assertThat(array).containsExactly(1, 2, 3, 4, 5); 205 | } 206 | 207 | @Test 208 | public void to_list() { 209 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 210 | 211 | List list = values.toList(); 212 | 213 | assertThat(list).containsExactly(1, 2, 3, 4, 5); 214 | } 215 | 216 | @Test 217 | public void to_set() { 218 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 219 | 220 | Set set = values.toSet(); 221 | 222 | assertThat(set).containsOnly(1, 2, 3, 4, 5); 223 | } 224 | 225 | @Test 226 | public void any_match() { 227 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 228 | 229 | assertThat(values.anyMatch(v -> v > 5)).isFalse(); 230 | assertThat(values.anyMatch(v -> v < 5)).isTrue(); 231 | } 232 | 233 | @Test 234 | public void all_match() { 235 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 236 | 237 | assertThat(values.allMatch(v -> v < 5)).isFalse(); 238 | assertThat(values.allMatch(v -> v < 6)).isTrue(); 239 | } 240 | 241 | @Test 242 | public void none_match() { 243 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 244 | 245 | assertThat(values.noneMatch(v -> v < 6)).isFalse(); 246 | assertThat(values.noneMatch(v -> v > 6)).isTrue(); 247 | } 248 | 249 | @Test 250 | public void collect() { 251 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 252 | 253 | Long count = values.collect(Collectors.counting()); 254 | 255 | assertThat(count).isEqualTo(5L); 256 | } 257 | 258 | @Test 259 | public void copy_into() { 260 | List list = Fluent.of(1, 2, 3, 4, 5).copyInto(new ArrayList<>()); 261 | 262 | assertThat(list).containsExactly(1, 2, 3, 4, 5); 263 | } 264 | 265 | @Test 266 | public void join_with_separator() { 267 | Fluent values = Fluent.of(1, null, 3, 4, 5); 268 | 269 | String string = values.join(", "); 270 | 271 | assertThat(string).isEqualTo("1, null, 3, 4, 5"); 272 | } 273 | 274 | @Test 275 | public void join() { 276 | Fluent values = Fluent.of(1, 2, 3, 4, 5); 277 | 278 | String string = values.join(); 279 | 280 | assertThat(string).isEqualTo("12345"); 281 | } 282 | 283 | @Test 284 | public void exclude_nulls() { 285 | Fluent values = Fluent.of(1, null, 3).notNulls(); 286 | 287 | assertThat(values).containsExactly(1, 3); 288 | } 289 | 290 | @Test 291 | public void limit() { 292 | Iterable values = Fluent.of(1, 2, 3, 4, 5).limit(2); 293 | 294 | assertThat(values).containsExactly(1, 2); 295 | } 296 | 297 | @Test 298 | public void first_match() { 299 | Optional firstMatch = Fluent.of(1, 2, 3, 4, 5).firstMatch(v -> v == 4); 300 | 301 | assertThat(firstMatch.get()).isEqualTo(4); 302 | } 303 | 304 | @Test 305 | public void of_type() { 306 | Fluent integers = Fluent.of("a", 2, "b", 3).filter(Integer.class); 307 | 308 | assertThat(integers).containsExactly(2, 3); 309 | } 310 | 311 | @Test 312 | public void cycle() { 313 | Iterable integers = Fluent.of(0, 1).cycle().limit(6); 314 | 315 | assertThat(integers).containsExactly(0, 1, 0, 1, 0, 1); 316 | } 317 | 318 | @Test 319 | public void for_each() { 320 | List list = new ArrayList<>(); 321 | 322 | Fluent.of("a", "b", "c").forEach(list::add); 323 | 324 | assertThat(list).containsExactly("a", "b", "c"); 325 | } 326 | 327 | @Test 328 | public void for_each_with_index() { 329 | List list = new ArrayList<>(); 330 | 331 | Fluent.of("a", "b", "c").forEachWithIndex((index, value) -> list.add(index + "-" + value)); 332 | 333 | assertThat(list).containsExactly("0-a", "1-b", "2-c"); 334 | } 335 | 336 | @Test 337 | public void to_stream() { 338 | Stream stream = Fluent.of("0", "1", "2").stream(); 339 | 340 | assertThat(stream.toArray()).containsExactly("0", "1", "2"); 341 | } 342 | 343 | @Test 344 | public void to_int_stream() { 345 | IntStream stream = Fluent.of("0", "1", "2").intStream(Integer::parseInt); 346 | 347 | assertThat(stream.iterator()).containsExactly(0, 1, 2); 348 | } 349 | 350 | @Test 351 | public void to_long_stream() { 352 | LongStream stream = Fluent.of("0", "1", "2").longStream(Long::parseLong); 353 | 354 | assertThat(stream.iterator()).containsExactly(0L, 1L, 2L); 355 | } 356 | 357 | @Test 358 | public void to_double_stream() { 359 | DoubleStream stream = Fluent.of("0", "1", "2").doubleStream(Double::parseDouble); 360 | 361 | assertThat(stream.iterator()).containsExactly(0D, 1D, 2D); 362 | } 363 | 364 | @Test 365 | public void min() { 366 | Optional min = Fluent.of(4, 3, 1, 5).min((v1, v2) -> (v1 - v2)); 367 | 368 | assertThat(min.get()).isEqualTo(1); 369 | } 370 | 371 | @Test 372 | public void max() { 373 | Optional min = Fluent.of(4, 3, 1, 5).max((v1, v2) -> (v1 - v2)); 374 | 375 | assertThat(min.get()).isEqualTo(5); 376 | } 377 | 378 | @Test 379 | public void reduce() { 380 | int sum = Fluent.of(1, 2, 3, 4).reduce(0, (a, b) -> a + b); 381 | 382 | assertThat(sum).isEqualTo(10); 383 | } 384 | 385 | @Test 386 | public void reduce_without_initial_value() { 387 | Optional sum = Fluent.of(1, 2, 3, 4).reduce((a, b) -> a + b); 388 | 389 | assertThat(sum.get()).isEqualTo(10); 390 | } 391 | 392 | @Test 393 | public void reduce_without_values() { 394 | Optional sum = Fluent.of().reduce((a, b) -> a + b); 395 | 396 | assertThat(sum.isPresent()).isFalse(); 397 | } 398 | 399 | @Test 400 | public void sorted_list() { 401 | List sorted = Fluent.of(5, 4, 3, 2, 1).toSortedList(Comparator.naturalOrder()); 402 | 403 | assertThat(sorted).containsExactly(1, 2, 3, 4, 5); 404 | } 405 | 406 | @Test 407 | public void sorted_set() { 408 | SortedSet sortedSet = Fluent.of(5, 4, 3, 2, 1).toSortedSet(Comparator.naturalOrder()); 409 | 410 | assertThat(sortedSet).containsExactly(1, 2, 3, 4, 5); 411 | } 412 | 413 | @Test 414 | public void unique_index_by() { 415 | Map index = Fluent.of("1", "22", "333").uniqueIndex(String::length); 416 | 417 | assertThat(index).containsEntry(1, "1").containsEntry(2, "22").containsEntry(3, "333"); 418 | } 419 | 420 | @Test(expected = IllegalArgumentException.class) 421 | public void key_conflict() { 422 | Fluent.of("a", "b").uniqueIndex(String::length); 423 | } 424 | 425 | @Test 426 | public void index_by() { 427 | Map> index = Fluent.of("a", "b", "cc", "dd").index(String::length); 428 | 429 | assertThat(index).containsEntry(1, Arrays.asList("a", "b")).containsEntry(2, Arrays.asList("cc", "dd")); 430 | } 431 | 432 | @Test 433 | public void to_map() { 434 | Map map = Fluent.of("a", "bb").toMap(String::length); 435 | 436 | assertThat(map).containsEntry("a", 1).containsEntry("bb", 2); 437 | } 438 | 439 | @Test 440 | public void get() { 441 | Fluent values = Fluent.of("a", "bb"); 442 | 443 | assertThat(values.get(0)).isEqualTo("a"); 444 | assertThat(values.get(1)).isEqualTo("bb"); 445 | } 446 | 447 | @Test(expected = ArrayIndexOutOfBoundsException.class) 448 | public void get_out_of_bounds() { 449 | Fluent.of("a", "bb").get(3); 450 | } 451 | 452 | @Test(expected = IllegalArgumentException.class) 453 | public void get_negative() { 454 | Fluent.of("a", "bb").get(-1); 455 | } 456 | 457 | @Test 458 | public void concatenate_array() { 459 | Fluent concatenated = Fluent.of("a", "bb").concat("cc", "dd"); 460 | 461 | assertThat(concatenated).containsExactly("a", "bb", "cc", "dd"); 462 | } 463 | 464 | @Test 465 | public void concatenate_iterable() { 466 | Fluent concatenated = Fluent.of("a", "b").concat(Fluent.of("c", "d")); 467 | 468 | assertThat(concatenated).containsExactly("a", "b", "c", "d"); 469 | } 470 | 471 | @Test 472 | public void flat_map() { 473 | Fluent letters = Fluent.of("a-a-a", "b-b").flatMap(s -> Arrays.asList(s.split("-"))); 474 | 475 | assertThat(letters).containsExactly("a", "a", "a", "b", "b"); 476 | } 477 | 478 | @Test 479 | public void get_only_element() { 480 | String only = Fluent.of("ONLY").getOnlyElement(); 481 | 482 | assertThat(only).isEqualTo("ONLY"); 483 | } 484 | 485 | @Test(expected = NoSuchElementException.class) 486 | public void get_only_element_of_empty_list() { 487 | Fluent.of().getOnlyElement(); 488 | } 489 | 490 | @Test 491 | public void iterate_on_characters() { 492 | Fluent chars = Fluent.ofChars("ABCDE"); 493 | 494 | assertThat(chars.toList()).containsExactly("A", "B", "C", "D", "E"); 495 | } 496 | 497 | @Test 498 | public void iterate_on_zero_characters() { 499 | Fluent chars = Fluent.ofChars(""); 500 | 501 | assertThat(chars.toList()).isEmpty(); 502 | } 503 | 504 | @Test 505 | public void split_string() { 506 | Fluent letters = Fluent.split("A,B,C,D", ","); 507 | 508 | assertThat(letters.toList()).containsExactly("A", "B", "C", "D"); 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /src/main/java/net/codestory/Fluent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013 all@code-story.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | package net.codestory; 17 | 18 | import java.util.*; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | import java.util.function.*; 22 | import java.util.regex.Pattern; 23 | import java.util.stream.*; 24 | 25 | import static java.util.Objects.requireNonNull; 26 | 27 | @FunctionalInterface 28 | public interface Fluent extends Iterable { 29 | Stream stream(); 30 | 31 | static Fluent of() { 32 | return Stream::empty; 33 | } 34 | 35 | @SafeVarargs 36 | static Fluent of(T... values) { 37 | requireNonNull(values); 38 | return () -> Stream.of(values); 39 | } 40 | 41 | static Fluent of(int[] values) { 42 | requireNonNull(values); 43 | return () -> IntStream.of(values).boxed(); 44 | } 45 | 46 | static Fluent of(double[] values) { 47 | requireNonNull(values); 48 | return () -> DoubleStream.of(values).boxed(); 49 | } 50 | 51 | static Fluent of(long[] values) { 52 | requireNonNull(values); 53 | return () -> LongStream.of(values).boxed(); 54 | } 55 | 56 | static Fluent of(Iterable values) { 57 | requireNonNull(values); 58 | return (values instanceof Fluent) ? (Fluent) values : () -> StreamSupport.stream(values.spliterator(), false); 59 | } 60 | 61 | static Fluent of(Iterator values) { 62 | requireNonNull(values); 63 | return () -> StreamSupport.stream(Spliterators.spliteratorUnknownSize(values, 0), false); 64 | } 65 | 66 | static Fluent of(Stream stream) { 67 | requireNonNull(stream); 68 | return () -> stream; 69 | } 70 | 71 | static Fluent ofChars(String text) { 72 | requireNonNull(text); 73 | 74 | return of(Stream.generate(new Supplier() { 75 | int index = 0; 76 | 77 | @Override 78 | public String get() { 79 | return text.substring(index, ++index); 80 | } 81 | })).limit(text.length()); 82 | } 83 | 84 | static Fluent split(String text, String regex) { 85 | requireNonNull(text); 86 | requireNonNull(regex); 87 | return of(Pattern.compile(regex).splitAsStream(text)); 88 | } 89 | 90 | public default void forEach(Consumer action) { 91 | requireNonNull(action); 92 | stream().forEach(action); 93 | } 94 | 95 | public default Fluent map(Function transform) { 96 | requireNonNull(transform); 97 | return () -> stream().map(transform); 98 | } 99 | 100 | public default IntStream mapToInt(ToIntFunction transform) { 101 | requireNonNull(transform); 102 | return stream().mapToInt(transform); 103 | } 104 | 105 | public default LongStream mapToLong(ToLongFunction transform) { 106 | requireNonNull(transform); 107 | return stream().mapToLong(transform); 108 | } 109 | 110 | public default DoubleStream mapToDouble(ToDoubleFunction transform) { 111 | requireNonNull(transform); 112 | return stream().mapToDouble(transform); 113 | } 114 | 115 | public default IntStream flatMapToInt(Function transform) { 116 | requireNonNull(transform); 117 | return stream().flatMapToInt(transform); 118 | } 119 | 120 | public default LongStream flatMapToLong(Function transform) { 121 | requireNonNull(transform); 122 | return stream().flatMapToLong(transform); 123 | } 124 | 125 | public default DoubleStream flatMapToDouble(Function transform) { 126 | requireNonNull(transform); 127 | return stream().flatMapToDouble(transform); 128 | } 129 | 130 | public default Fluent filter(Predicate predicate) { 131 | requireNonNull(predicate); 132 | return () -> stream().filter(predicate); 133 | } 134 | 135 | public default Fluent exclude(Predicate predicate) { 136 | requireNonNull(predicate); 137 | return () -> stream().filter(predicate.negate()); 138 | } 139 | 140 | @SuppressWarnings("unchecked") 141 | public default Fluent filter(Class type) { 142 | requireNonNull(type); 143 | return (Fluent) filter(value -> type.isInstance(value)); 144 | } 145 | 146 | public default long size() { 147 | return stream().count(); 148 | } 149 | 150 | public default long count(Predicate predicate) { 151 | requireNonNull(predicate); 152 | return stream().filter(predicate).count(); 153 | } 154 | 155 | public default Optional first() { 156 | return stream().findFirst(); 157 | } 158 | 159 | public default Optional any() { 160 | return stream().findAny(); 161 | } 162 | 163 | public default Fluent skip(long n) { 164 | return () -> stream().skip(n); 165 | } 166 | 167 | public default Optional firstMatch(Predicate predicate) { 168 | requireNonNull(predicate); 169 | return stream().filter(predicate).findFirst(); 170 | } 171 | 172 | public default Optional last() { 173 | return stream().reduce((l, r) -> r); 174 | } 175 | 176 | public default boolean isEmpty() { 177 | return !iterator().hasNext(); 178 | } 179 | 180 | public default String join(CharSequence delimiter) { 181 | requireNonNull(delimiter); 182 | StringJoiner joiner = new StringJoiner(delimiter); 183 | forEach(value -> joiner.add(String.valueOf(value))); 184 | return joiner.toString(); 185 | } 186 | 187 | public default String join() { 188 | return join(""); 189 | } 190 | 191 | public default boolean contains(Object element) { 192 | return stream().anyMatch(Predicate.isEqual(element)); 193 | } 194 | 195 | public default int indexOf(Object element) { 196 | int index = 0; 197 | for (T value : this) { 198 | if (Objects.equals(value, element)) { 199 | return index; 200 | } 201 | index++; 202 | } 203 | return -1; 204 | } 205 | 206 | public default T[] toArray(IntFunction generator) { 207 | requireNonNull(generator); 208 | return stream().toArray(generator); 209 | } 210 | 211 | public default List toList() { 212 | return copyInto(new ArrayList<>()); 213 | } 214 | 215 | public default List toList(int size) { 216 | return copyInto(new ArrayList<>(size)); 217 | } 218 | 219 | public default List toSortedList(Comparator comparator) { 220 | requireNonNull(comparator); 221 | List list = copyInto(new ArrayList<>()); 222 | Collections.sort(list, comparator); 223 | return list; 224 | } 225 | 226 | public default SortedSet toSortedSet(Comparator comparator) { 227 | requireNonNull(comparator); 228 | return copyInto(new TreeSet<>(comparator)); 229 | } 230 | 231 | public default Set toSet() { 232 | return copyInto(new HashSet<>()); 233 | } 234 | 235 | public default boolean anyMatch(Predicate predicate) { 236 | requireNonNull(predicate); 237 | return stream().anyMatch(predicate); 238 | } 239 | 240 | public default boolean allMatch(Predicate predicate) { 241 | requireNonNull(predicate); 242 | return stream().allMatch(predicate); 243 | } 244 | 245 | public default boolean noneMatch(Predicate predicate) { 246 | requireNonNull(predicate); 247 | return stream().noneMatch(predicate); 248 | } 249 | 250 | public default R collect(Collector collector) { 251 | requireNonNull(collector); 252 | return stream().collect(collector); 253 | } 254 | 255 | public default Fluent limit(int limitSize) { 256 | if (limitSize < 0) { 257 | throw new IllegalArgumentException("limit is negative"); 258 | } 259 | return () -> stream().limit(limitSize); 260 | } 261 | 262 | public default Fluent cycle() { 263 | return () -> Stream.generate(() -> stream()).flatMap(s -> s); 264 | } 265 | 266 | public default void forEachWithIndex(BiConsumer consumer) { 267 | requireNonNull(consumer); 268 | if (isParallel()) { 269 | AtomicInteger index = new AtomicInteger(0); 270 | forEach(value -> consumer.accept(index.getAndIncrement(), value)); 271 | } else { 272 | int index = 0; 273 | for (T value : this) { 274 | consumer.accept(index++, value); 275 | } 276 | } 277 | } 278 | 279 | public default Fluent peek(Consumer action) { 280 | requireNonNull(action); 281 | return () -> stream().peek(action); 282 | } 283 | 284 | public default void forEachOrdered(Consumer action) { 285 | requireNonNull(action); 286 | stream().forEachOrdered(action); 287 | } 288 | 289 | public default Iterator iterator() { 290 | return stream().iterator(); 291 | } 292 | 293 | public default Spliterator spliterator() { 294 | return stream().spliterator(); 295 | } 296 | 297 | public default boolean isParallel() { 298 | return stream().isParallel(); 299 | } 300 | 301 | public default Fluent sequential() { 302 | return () -> stream().sequential(); 303 | } 304 | 305 | public default Fluent unordered() { 306 | return () -> stream().unordered(); 307 | } 308 | 309 | public default IntStream intStream(ToIntFunction mapper) { 310 | requireNonNull(mapper); 311 | return stream().mapToInt(mapper); 312 | } 313 | 314 | public default LongStream longStream(ToLongFunction mapper) { 315 | requireNonNull(mapper); 316 | return stream().mapToLong(mapper); 317 | } 318 | 319 | public default DoubleStream doubleStream(ToDoubleFunction mapper) { 320 | requireNonNull(mapper); 321 | return stream().mapToDouble(mapper); 322 | } 323 | 324 | public default Optional min(Comparator comparator) { 325 | requireNonNull(comparator); 326 | return stream().min(comparator); 327 | } 328 | 329 | public default Optional max(Comparator comparator) { 330 | requireNonNull(comparator); 331 | return stream().max(comparator); 332 | } 333 | 334 | public default > C copyInto(C collection) { 335 | requireNonNull(collection); 336 | return stream().collect(Collectors.toCollection(() -> collection)); 337 | } 338 | 339 | public default T reduce(T identity, BinaryOperator accumulator) { 340 | requireNonNull(accumulator); 341 | return stream().reduce(identity, accumulator); 342 | } 343 | 344 | public default Optional reduce(BinaryOperator accumulator) { 345 | requireNonNull(accumulator); 346 | return stream().reduce(accumulator); 347 | } 348 | 349 | public default Map uniqueIndex(Function toKey) { 350 | requireNonNull(toKey); 351 | 352 | Map map = isParallel() ? new ConcurrentHashMap<>() : new HashMap<>(); 353 | 354 | forEach(value -> { 355 | K key = toKey.apply(value); 356 | if (null != map.put(key, value)) { 357 | throw new IllegalArgumentException("Same key used twice " + key + " " + value); 358 | } 359 | }); 360 | 361 | return map; 362 | } 363 | 364 | public default Map> index(Function toKey) { 365 | requireNonNull(toKey); 366 | 367 | Map> multiMap = isParallel() ? new ConcurrentHashMap<>() : new HashMap<>(); 368 | 369 | forEach(value -> { 370 | K key = toKey.apply(value); 371 | 372 | List list = multiMap.computeIfAbsent(key, k -> new ArrayList()); 373 | list.add(value); 374 | }); 375 | 376 | return multiMap; 377 | } 378 | 379 | public default Map toMap(Function toValue) { 380 | return toMap(k -> k, toValue); 381 | } 382 | 383 | public default Map toMap(Function toKey, Function toValue) { 384 | return MapUtils.toMap(this, toKey, toValue, isParallel() ? ConcurrentHashMap::new : HashMap::new); 385 | } 386 | 387 | public default Map toLenientMap(Function toKey, Function toValue) { 388 | return MapUtils.toLenientMap(this, toKey, toValue, isParallel() ? ConcurrentHashMap::new : HashMap::new); 389 | } 390 | 391 | @SuppressWarnings("unchecked") 392 | public default SortedMap toSortedMap(Function toKey, Function toValue) { 393 | if (isParallel()) { 394 | return (TreeMap) MapUtils.toMap(this, toKey, toValue, () -> Collections.synchronizedMap(new TreeMap<>())); 395 | } else { 396 | return (TreeMap) MapUtils.toMap(this, toKey, toValue, TreeMap::new); 397 | } 398 | } 399 | 400 | public default SortedMap toLenientSortedMap(Function toKey, Function toValue) { 401 | if (isParallel()) { 402 | return (TreeMap) MapUtils.toLenientMap(this, toKey, toValue, () -> Collections.synchronizedMap(new TreeMap<>())); 403 | } else { 404 | return (TreeMap) MapUtils.toLenientMap(this, toKey, toValue, TreeMap::new); 405 | } 406 | } 407 | 408 | public default T get(int index) { 409 | if (index < 0) { 410 | throw new IllegalArgumentException("index is negative"); 411 | } 412 | 413 | int current = 0; 414 | for (T next : this) { 415 | if (current == index) { 416 | return next; 417 | } 418 | current++; 419 | } 420 | throw new ArrayIndexOutOfBoundsException(); 421 | } 422 | 423 | public default > Fluent flatMap(Function toCollection) { 424 | requireNonNull(toCollection); 425 | return () -> { 426 | Function> toStream = l -> l.stream(); 427 | Function> toListAndThenToStream = toCollection.andThen(toStream); 428 | return stream().flatMap(toListAndThenToStream); 429 | }; 430 | } 431 | 432 | public default Fluent parallel() { 433 | return () -> stream().parallel(); 434 | } 435 | 436 | public default Fluent sorted() { 437 | return () -> stream().sorted(); 438 | } 439 | 440 | public default Fluent distinct() { 441 | return () -> stream().distinct(); 442 | } 443 | 444 | public default Fluent sorted(Comparator comparator) { 445 | requireNonNull(comparator); 446 | return () -> stream().sorted(comparator); 447 | } 448 | 449 | public default Fluent reversed(Comparator comparator) { 450 | requireNonNull(comparator); 451 | return () -> stream().sorted(comparator.reversed()); 452 | } 453 | 454 | public default > Fluent sortedOn(Function comparator) { 455 | requireNonNull(comparator); 456 | return () -> stream().sorted((l, r) -> comparator.apply(l).compareTo(comparator.apply(r))); 457 | } 458 | 459 | public default > Fluent reversedOn(Function comparator) { 460 | requireNonNull(comparator); 461 | return () -> stream().sorted((l, r) -> comparator.apply(r).compareTo(comparator.apply(l))); 462 | } 463 | 464 | //@SafeVarargs 465 | public default Fluent concat(T... values) { 466 | requireNonNull(values); 467 | return () -> Stream.concat(stream(), Stream.of(values)); 468 | } 469 | 470 | public default Fluent concat(Iterable values) { 471 | requireNonNull(values); 472 | return () -> Stream.concat(stream(), StreamSupport.stream(values.spliterator(), false)); 473 | } 474 | 475 | public default T getOnlyElement() { 476 | return first().get(); 477 | } 478 | 479 | public default Fluent notNulls() { 480 | return filter(v -> v != null); 481 | } 482 | 483 | public default Map> groupBy(Function classifier) { 484 | requireNonNull(classifier); 485 | return stream().collect(Collectors.groupingBy(classifier, HashMap::new, Collectors.toList())); 486 | } 487 | 488 | public default >> Map> groupBy(Function classifier, Supplier mapFactory) { 489 | requireNonNull(classifier); 490 | return stream().collect(Collectors.groupingBy(classifier, mapFactory, Collectors.toList())); 491 | } 492 | 493 | class MapUtils { 494 | private MapUtils() { 495 | // Static class 496 | } 497 | 498 | private static Map toMap(Iterable values, Function toKey, Function toValue, Supplier> mapSupplier) { 499 | requireNonNull(toKey); 500 | requireNonNull(toValue); 501 | 502 | Map map = mapSupplier.get(); 503 | 504 | values.forEach(item -> { 505 | K key = toKey.apply(item); 506 | V value = toValue.apply(item); 507 | if (null != map.put(key, value)) { 508 | throw new IllegalArgumentException("Same key used twice " + key + " " + value); 509 | } 510 | }); 511 | 512 | return map; 513 | } 514 | 515 | private static Map toLenientMap(Iterable values, Function toKey, Function toValue, Supplier> mapSupplier) { 516 | requireNonNull(toKey); 517 | requireNonNull(toValue); 518 | 519 | Map map = mapSupplier.get(); 520 | 521 | values.forEach(item -> { 522 | K key = toKey.apply(item); 523 | V value = toValue.apply(item); 524 | map.put(key, value); 525 | }); 526 | 527 | return map; 528 | } 529 | } 530 | } 531 | --------------------------------------------------------------------------------