This is an automated email from the ASF dual-hosted git repository. robertlazarski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 08884a006375e1309829302123dad2dfcacb4c25 Author: Robert Lazarski <[email protected]> AuthorDate: Sun Sep 6 09:17:38 2026 -1000 Replace google-java-format with a local re-indenter Codegen pulled guava and a shaded javac -- about 6 MB in every axis2.war -- to tidy generated Java that a template had already laid out. PrettyPrinter now rewrites leading whitespace and nothing else: no reflow, no import reordering, no character the compiler can see, so the worst a bug here does is indent badly. Braces in literals and comments are skipped and text blocks are copied verbatim, since their leading whitespace is string content. A failure now leaves the file as generated; the old code deleted the source before renaming over it. Guava stays pinned in dependencyManagement -- it still arrives test-scoped via truth. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- legal/guava-LICENSE.txt | 203 --------------------- modules/codegen/pom.xml | 5 - .../src/org/apache/axis2/util/PrettyPrinter.java | 190 +++++++++++++++++-- .../org/apache/axis2/util/PrettyPrinterTest.java | 193 ++++++++++++++++++++ src/site/markdown/release-notes/2.0.2.md | 14 ++ 5 files changed, 379 insertions(+), 226 deletions(-) diff --git a/legal/guava-LICENSE.txt b/legal/guava-LICENSE.txt deleted file mode 100644 index 6b0b1270ff..0000000000 --- a/legal/guava-LICENSE.txt +++ /dev/null @@ -1,203 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. - diff --git a/modules/codegen/pom.xml b/modules/codegen/pom.xml index c82e6a02b6..c093a470e2 100644 --- a/modules/codegen/pom.xml +++ b/modules/codegen/pom.xml @@ -53,11 +53,6 @@ <artifactId>axis2-adb</artifactId> <version>${project.version}</version> </dependency> - <dependency> - <groupId>com.google.googlejavaformat</groupId> - <artifactId>google-java-format</artifactId> - <version>1.7</version> - </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>axis2-transport-local</artifactId> diff --git a/modules/codegen/src/org/apache/axis2/util/PrettyPrinter.java b/modules/codegen/src/org/apache/axis2/util/PrettyPrinter.java index d51336cd5f..7cec9c5e52 100644 --- a/modules/codegen/src/org/apache/axis2/util/PrettyPrinter.java +++ b/modules/codegen/src/org/apache/axis2/util/PrettyPrinter.java @@ -16,40 +16,194 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.axis2.util; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import com.google.common.base.Charsets; -import com.google.common.io.Files; -import com.google.googlejavaformat.java.Formatter; - -import java.io.File; - /** - * Tidies up the java source code. + * Tidies up generated Java source by re-indenting it. + * <p> + * This used to call google-java-format, which is a full parse-and-reflow + * formatter and brought guava and a shaded javac with it -- about 6 MB of + * dependency, shipped in {@code axis2.war}, to tidy machine-generated code that + * is already well formed because a template emitted it. This replacement has no + * dependencies. + * <p> + * <b>What it does, and deliberately does not do.</b> It rewrites leading + * whitespace and nothing else. It does not reflow long lines, reorder imports, + * insert or remove blank lines, or touch a single character that the compiler + * can see. That is what makes it safe to run over generated source: the worst a + * bug here can produce is ugly indentation, not code that changed meaning or + * stopped compiling. Braces are counted with string literals, character + * literals and comments skipped, so a {@code "}"} inside a string does not + * shift the indent of everything after it. + * <p> + * Text blocks are reproduced verbatim, because their leading whitespace is + * content: re-indenting one would silently change the string a caller gets back. */ public class PrettyPrinter { private static final Log log = LogFactory.getLog(PrettyPrinter.class); + private static final String INDENT = " "; + + private PrettyPrinter() { + } /** - * Pretty prints contents of the java source file. + * Pretty prints contents of the java source file, in place. + * <p> + * The file is left exactly as it was if anything goes wrong. The previous + * implementation deleted the source before renaming the formatted copy over + * it, so a formatting failure destroyed the generated file. * - * @param file + * @param file the java source file to re-indent */ public static void prettify(File file) { - File formattedFile = new File(file.getParentFile(), file.getName() + ".new"); try { - new Formatter().formatSource( - Files.asCharSource(file, Charsets.UTF_8), - Files.asCharSink(formattedFile, Charsets.UTF_8)); - } catch (Exception e) { - log.warn("Exception occurred while trying to pretty print file " + file, e); + String source = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + String formatted = format(source); + if (!formatted.equals(source)) { + Files.write(file.toPath(), formatted.getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + log.warn("Could not pretty print " + file + "; leaving it as generated", e); + } catch (RuntimeException e) { + log.warn("Could not pretty print " + file + "; leaving it as generated", e); + } + } + + /** + * Re-indents Java source. + * + * @param source the source to format + * @return the source with leading whitespace rewritten + */ + static String format(String source) { + String lineSeparator = source.indexOf("\r\n") >= 0 ? "\r\n" : "\n"; + List<String> out = new ArrayList<String>(); + Scanner scanner = new Scanner(); + int depth = 0; + + for (String raw : source.split("\r\n|\n|\r", -1)) { + if (scanner.inTextBlock) { + // Leading whitespace here is part of the string value. + out.add(raw); + scanner.scan(raw); + continue; + } + String trimmed = raw.trim(); + if (trimmed.isEmpty()) { + out.add(""); + scanner.scan(raw); + continue; + } + if (scanner.inBlockComment) { + out.add(indent(depth) + (trimmed.startsWith("*") ? " " + trimmed : trimmed)); + scanner.scan(raw); + continue; + } + // A line that starts by closing a block sits at the level of the block + // it closes first, however many it closes -- "}}" aligns with the inner + // one, the same place the first token would go on a line of its own. + int outdent = trimmed.charAt(0) == '}' ? 1 : 0; + out.add(indent(Math.max(depth - outdent, 0)) + trimmed); + depth = Math.max(depth + scanner.scan(raw), 0); + } + return String.join(lineSeparator, out); + } + + private static String indent(int depth) { + StringBuilder sb = new StringBuilder(depth * INDENT.length()); + for (int i = 0; i < depth; i++) { + sb.append(INDENT); + } + return sb.toString(); + } + + /** + * Walks a line counting braces, carrying block-comment and text-block state + * across lines. Only braces outside literals and comments are counted. + */ + private static final class Scanner { + boolean inBlockComment; + boolean inTextBlock; + + /** @return the net brace delta contributed by this line */ + int scan(String line) { + int delta = 0; + int i = 0; + int n = line.length(); + while (i < n) { + char c = line.charAt(i); + if (inBlockComment) { + if (c == '*' && i + 1 < n && line.charAt(i + 1) == '/') { + inBlockComment = false; + i += 2; + } else { + i++; + } + continue; + } + if (inTextBlock) { + if (c == '"' && i + 2 < n + 1 && line.startsWith("\"\"\"", i)) { + inTextBlock = false; + i += 3; + } else { + i++; + } + continue; + } + if (c == '/' && i + 1 < n && line.charAt(i + 1) == '/') { + return delta; // rest of the line is a comment + } + if (c == '/' && i + 1 < n && line.charAt(i + 1) == '*') { + inBlockComment = true; + i += 2; + continue; + } + if (c == '"' && line.startsWith("\"\"\"", i)) { + inTextBlock = true; + i += 3; + continue; + } + if (c == '"' || c == '\'') { + i = skipLiteral(line, i, c); + continue; + } + if (c == '{') { + delta++; + } else if (c == '}') { + delta--; + } + i++; + } + return delta; + } + + /** @return the index just past the closing quote, or the line end if unterminated */ + private int skipLiteral(String line, int start, char quote) { + int i = start + 1; + int n = line.length(); + while (i < n) { + char c = line.charAt(i); + if (c == '\\') { + i += 2; + continue; + } + if (c == quote) { + return i + 1; + } + i++; + } + return n; } - file.delete(); - formattedFile.renameTo(file); } } diff --git a/modules/codegen/test/org/apache/axis2/util/PrettyPrinterTest.java b/modules/codegen/test/org/apache/axis2/util/PrettyPrinterTest.java new file mode 100644 index 0000000000..747248d1a2 --- /dev/null +++ b/modules/codegen/test/org/apache/axis2/util/PrettyPrinterTest.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.axis2.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import org.junit.jupiter.api.Test; + +/** + * The formatter rewrites leading whitespace and nothing else. These pin that + * property, because it is what makes running it over generated source safe: the + * worst a bug can do is indent badly, never change what the code means. + */ +public class PrettyPrinterTest { + + /** Everything except leading whitespace must survive byte for byte. */ + private void assertOnlyIndentChanged(String source) { + String formatted = PrettyPrinter.format(source); + String[] before = source.split("\n", -1); + String[] after = formatted.split("\n", -1); + assertEquals(before.length, after.length, "line count must not change"); + for (int i = 0; i < before.length; i++) { + assertEquals(before[i].trim(), after[i].trim(), + "line " + (i + 1) + " changed beyond its indentation"); + } + } + + @Test + public void testNestingIsIndented() { + String out = PrettyPrinter.format( + "public class A {\n" + + "public void m() {\n" + + "if (x) {\n" + + "y();\n" + + "}\n" + + "}\n" + + "}\n"); + assertEquals( + "public class A {\n" + + " public void m() {\n" + + " if (x) {\n" + + " y();\n" + + " }\n" + + " }\n" + + "}\n", out); + } + + /** A brace inside a string must not shift everything after it. */ + @Test + public void testBracesInStringLiteralsAreNotCounted() { + String out = PrettyPrinter.format( + "class A {\n" + + "String s = \"{{{\";\n" + + "int i = 1;\n" + + "}\n"); + assertEquals( + "class A {\n" + + " String s = \"{{{\";\n" + + " int i = 1;\n" + + "}\n", out); + } + + @Test + public void testBracesInCharLiteralsAndCommentsAreNotCounted() { + String out = PrettyPrinter.format( + "class A {\n" + + "char c = '{';\n" + + "// } not a closer\n" + + "/* { neither */\n" + + "int i = 1;\n" + + "}\n"); + assertTrue(out.contains(" char c = '{';"), out); + assertTrue(out.contains(" int i = 1;"), out); + assertTrue(out.endsWith("}\n"), out); + } + + /** An escaped quote must not be read as the end of the literal. */ + @Test + public void testEscapedQuotesInsideLiterals() { + assertOnlyIndentChanged("class A {\nString s = \"a\\\"{b\";\nint i = 1;\n}\n"); + String out = PrettyPrinter.format("class A {\nString s = \"a\\\"{b\";\nint i = 1;\n}\n"); + assertTrue(out.contains(" int i = 1;"), out); + } + + /** + * Leading whitespace inside a text block is part of the string. Re-indenting + * one would silently change the value a caller gets back. + */ + @Test + public void testTextBlockContentIsUntouched() { + String source = + "class A {\n" + + "String s = \"\"\"\n" + + " keep this\n" + + " and this\n" + + "\"\"\";\n" + + "}\n"; + String out = PrettyPrinter.format(source); + assertTrue(out.contains(" keep this\n"), out); + assertTrue(out.contains(" and this\n"), out); + } + + @Test + public void testBlockCommentContinuationLinesAlign() { + String out = PrettyPrinter.format( + "class A {\n" + + "/**\n" + + "* javadoc\n" + + "*/\n" + + "void m() {}\n" + + "}\n"); + assertTrue(out.contains(" /**\n * javadoc\n */"), out); + } + + @Test + public void testBlankLinesStayBlankAndCountIsPreserved() { + assertOnlyIndentChanged("class A {\n\n \nvoid m() {}\n\n}\n"); + assertTrue(PrettyPrinter.format("class A {\n\n \nvoid m() {}\n}\n").contains("\n\n\n")); + } + + @Test + public void testClosingBraceOnASharedLine() { + String out = PrettyPrinter.format( + "class A {\n" + + "if (x) {\n" + + "a();\n" + + "} else {\n" + + "b();\n" + + "}\n" + + "}\n"); + assertTrue(out.contains(" } else {"), out); + assertTrue(out.contains(" b();"), out); + } + + @Test + public void testMultipleClosersOnOneLine() { + String out = PrettyPrinter.format("class A {\nvoid m() {\nif (x) {\na();\n}}\n}\n"); + assertTrue(out.contains(" }}"), out); + } + + /** Nothing pathological should make it drop or add lines. */ + @Test + public void testUnterminatedLiteralDoesNotRunAway() { + assertOnlyIndentChanged("class A {\nString s = \"unterminated;\nint i = 1;\n}\n"); + } + + @Test + public void testCrlfInputKeepsCrlf() { + String out = PrettyPrinter.format("class A {\r\nint i = 1;\r\n}\r\n"); + assertTrue(out.contains("\r\n"), "CRLF input must stay CRLF"); + assertTrue(out.contains("\r\n int i = 1;\r\n"), out.replace("\r", "\\r")); + } + + /** A formatting failure must never destroy the generated file. */ + @Test + public void testFileIsLeftIntactAndFormattedInPlace() throws Exception { + File f = File.createTempFile("axis2-prettyprint", ".java"); + try { + Files.write(f.toPath(), "class A {\nint i = 1;\n}\n".getBytes(StandardCharsets.UTF_8)); + PrettyPrinter.prettify(f); + String out = new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8); + assertEquals("class A {\n int i = 1;\n}\n", out); + } finally { + f.delete(); + } + } + + @Test + public void testAMissingFileIsSurvivable() { + PrettyPrinter.prettify(new File("/nonexistent/axis2-no-such-file.java")); + } +} diff --git a/src/site/markdown/release-notes/2.0.2.md b/src/site/markdown/release-notes/2.0.2.md index e4abc6d5e0..648d38cb80 100644 --- a/src/site/markdown/release-notes/2.0.2.md +++ b/src/site/markdown/release-notes/2.0.2.md @@ -26,6 +26,20 @@ Apache Axis2 2.0.2 Release Notes could not work without the four jars deliberately left out. Axis2's own `axis2-spring` is unchanged and still shipped. +- **google-java-format replaced, dropping ~5.9 MB from the WAR.** Codegen tidied its + generated Java with google-java-format, a full parse-and-reflow formatter that brought + guava and a shaded javac with it -- about 6 MB shipped in every `axis2.war` to format + machine-generated code that a template had already laid out. `PrettyPrinter` now + re-indents with no dependencies. It rewrites leading whitespace and nothing else: it + does not reflow lines, reorder imports or alter a character the compiler can see, so + the worst it can do is indent badly rather than change what generated code means. + Braces inside string literals, character literals and comments are not counted, and + text blocks are reproduced verbatim because their leading whitespace is part of the + string. A formatting failure now leaves the generated file untouched -- the previous + code deleted the source before renaming the formatted copy over it, so a failure + destroyed it. `axis2.war` goes from 36 MB to 30 MB across this and the JMS, mail and + Spring changes above. + - **The mail transport is no longer bundled in `axis2.war` either.** Same reasoning as JMS: no default `axis2.xml` enables a `mailto` listener or sender, and the module is deprecated for removal in 2.1.0. It remains in `lib/` of the binary distribution.
