From d7282048904bb1fef1289c81b2ba9cd1ce63bfde Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 13 Sep 2015 18:12:33 +0200 Subject: [PATCH] Add errorprone check and fix found errors Adds gradle-errorprone-plugin 0.0.8, requires Gradle 2.6. --- build.gradle | 8 ++++++++ .../jivesoftware/smack/bosh/XMPPBOSHConnection.java | 12 +++++++++--- .../org/jivesoftware/smack/SASLAuthentication.java | 9 +++++++-- .../smack/proxy/HTTPProxySocketConnection.java | 11 ++++++----- .../jivesoftware/smack/util/ObservableReader.java | 2 +- .../jivesoftware/smack/util/ObservableWriter.java | 2 +- .../smackx/bytestreams/socks5/Socks5Client.java | 1 - .../smackx/offline/packet/OfflineMessageRequest.java | 2 +- .../jivesoftware/smackx/xdata/packet/DataForm.java | 7 +++++-- .../smackx/xhtmlim/packet/XHTMLExtension.java | 2 +- .../smackx/bytestreams/socks5/Socks5TestProxy.java | 8 ++++---- .../smack/inttest/SmackIntegrationTestFramework.java | 2 +- .../smack/inttest/util/ResultSyncPoint.java | 7 ++++++- .../smack/java7/XmppHostnameVerifier.java | 4 ++-- .../jivesoftware/smackx/jingleold/JingleManager.java | 1 + .../jingleold/mediaimpl/jmf/AudioReceiver.java | 2 +- .../smackx/jingleold/nat/TransportCandidate.java | 1 + .../smackx/workgroup/agent/AgentRoster.java | 6 ++---- .../smackx/workgroup/packet/AgentStatusRequest.java | 3 +-- .../smackx/workgroup/packet/OccupantsInfo.java | 6 +++--- .../smackx/workgroup/packet/QueueDetails.java | 9 +-------- .../smackx/xroster/packet/RosterExchange.java | 2 +- .../org/jivesoftware/smack/tcp/PacketWriterTest.java | 1 + 23 files changed, 64 insertions(+), 44 deletions(-) diff --git a/build.gradle b/build.gradle index 2b9db5ca6..67ac39587 100644 --- a/build.gradle +++ b/build.gradle @@ -3,12 +3,14 @@ import org.gradle.plugins.signing.Sign buildscript { repositories { jcenter() + maven { url 'https://plugins.gradle.org/m2/' } maven { url 'http://dl.bintray.com/content/aalmiray/kordamp' } } dependencies { classpath 'org.kordamp:markdown-gradle-plugin:1.0.0' classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.0' classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.3.1" + classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.8' } } apply plugin: 'org.kordamp.gradle.markdown' @@ -19,6 +21,7 @@ allprojects { apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'jacoco' + apply plugin: 'net.ltgt.errorprone' ext { gitCommit = getGitCommit() @@ -127,6 +130,11 @@ allprojects { // e.g. JAVA7_HOME. See SMACK-651. '-Xlint:-options', '-Werror', + // Needed because since adding gradle-errorprone-plugin + // See https://github.com/tbroyer/gradle-errorprone-plugin/issues/15 + '-Xlint:-path', + // Disable errorprone checks + '-Xep:TypeParameterUnusedInFormals:OFF', ] } diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java index e05ec803a..048e0942a 100644 --- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java +++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java @@ -100,6 +100,8 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { */ protected String sessionID = null; + private boolean notified; + /** * Create a HTTP Binding connection to an XMPP server. * @@ -135,6 +137,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { @Override protected void connectInternal() throws SmackException, InterruptedException { done = false; + notified = false; try { // Ensure a clean starting state if (client != null) { @@ -179,10 +182,12 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { // Wait for the response from the server synchronized (this) { if (!connected) { - try { - wait(getPacketReplyTimeout()); + final long deadline = System.currentTimeMillis() + getPacketReplyTimeout(); + while (!notified) { + final long now = System.currentTimeMillis(); + if (now > deadline) break; + wait(deadline - now); } - catch (InterruptedException e) {} } } @@ -439,6 +444,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { } } finally { + notified = true; synchronized (XMPPBOSHConnection.this) { XMPPBOSHConnection.this.notifyAll(); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java index 24e203528..9c22cce01 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java @@ -192,8 +192,13 @@ public final class SASLAuthentication { else { currentMechanism.authenticate(username, host, xmppServiceDomain, password); } - // Wait until SASL negotiation finishes - wait(connection.getPacketReplyTimeout()); + final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout(); + while (!authenticationSuccessful && saslException == null) { + final long now = System.currentTimeMillis(); + if (now > deadline) break; + // Wait until SASL negotiation finishes + wait(deadline - now); + } } if (saslException != null){ diff --git a/smack-core/src/main/java/org/jivesoftware/smack/proxy/HTTPProxySocketConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/proxy/HTTPProxySocketConnection.java index 6087f09ca..d818e6164 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/proxy/HTTPProxySocketConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/proxy/HTTPProxySocketConnection.java @@ -71,7 +71,12 @@ class HTTPProxySocketConnection implements ProxySocketConnection { while (true) { - char c = (char) in.read(); + int inByte = in.read(); + if (inByte == -1) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP); + } + char c = (char) inByte; got.append(c); if (got.length() > 1024) { @@ -79,10 +84,6 @@ class HTTPProxySocketConnection implements ProxySocketConnection { "header of >1024 characters from " + proxyhost + ", cancelling connection"); } - if (c == -1) - { - throw new ProxyException(ProxyInfo.ProxyType.HTTP); - } if ((nlchars == 0 || nlchars == 2) && c == '\r') { nlchars++; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableReader.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableReader.java index 252d1c794..3cd27ff2f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableReader.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableReader.java @@ -31,7 +31,7 @@ import java.util.List; public class ObservableReader extends Reader { Reader wrappedReader = null; - List listeners = new ArrayList(); + final List listeners = new ArrayList(); public ObservableReader(Reader wrappedReader) { this.wrappedReader = wrappedReader; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableWriter.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableWriter.java index edb8aa1eb..8a7a754de 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableWriter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ObservableWriter.java @@ -31,7 +31,7 @@ public class ObservableWriter extends Writer { private static final int MAX_STRING_BUILDER_SIZE = 4096; Writer wrappedWriter = null; - List listeners = new ArrayList(); + final List listeners = new ArrayList(); private final StringBuilder stringBuilder = new StringBuilder(MAX_STRING_BUILDER_SIZE); public ObservableWriter(Writer wrappedWriter) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java index f51526fac..f58a93735 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java @@ -75,7 +75,6 @@ class Socks5Client { */ public Socket getSocket(int timeout) throws IOException, XMPPErrorException, InterruptedException, TimeoutException, SmackException, XMPPException { - // wrap connecting in future for timeout FutureTask futureTask = new FutureTask(new Callable() { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/packet/OfflineMessageRequest.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/packet/OfflineMessageRequest.java index 6aba162e4..13e6b04dc 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/packet/OfflineMessageRequest.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/packet/OfflineMessageRequest.java @@ -38,7 +38,7 @@ public class OfflineMessageRequest extends IQ { public static final String ELEMENT = "offline"; public static final String NAMESPACE = "http://jabber.org/protocol/offline"; - private List items = new ArrayList(); + private final List items = new ArrayList<>(); private boolean purge = false; private boolean fetch = false; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java index 577c06831..34c53156d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java @@ -70,7 +70,7 @@ public class DataForm implements ExtensionElement { private Type type; private String title; - private List instructions = new ArrayList(); + private final List instructions = new ArrayList<>(); private ReportedData reportedData; private final List items = new ArrayList(); private final Map fields = new LinkedHashMap<>(); @@ -197,7 +197,10 @@ public class DataForm implements ExtensionElement { * @param instructions list of instructions that explain how to fill out the form. */ public void setInstructions(List instructions) { - this.instructions = instructions; + synchronized (this.instructions) { + this.instructions.clear(); + this.instructions.addAll(instructions); + } } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/packet/XHTMLExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/packet/XHTMLExtension.java index a777d2154..858e32650 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/packet/XHTMLExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/packet/XHTMLExtension.java @@ -40,7 +40,7 @@ public class XHTMLExtension implements ExtensionElement { public static final String ELEMENT = "html"; public static final String NAMESPACE = "http://jabber.org/protocol/xhtml-im"; - private List bodies = new ArrayList(); + private final List bodies = new ArrayList<>(); /** * Returns the XML element name of the extension sub-packet root element. diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5TestProxy.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5TestProxy.java index f40eb930b..53bfa3fb2 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5TestProxy.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5TestProxy.java @@ -173,6 +173,7 @@ public final class Socks5TestProxy { * @param digest identifying the connection * @return socket or null if there is no socket for the given digest */ + @SuppressWarnings("WaitNotInLoop") public Socket getSocket(String digest) { synchronized(this) { if (!startupComplete) { @@ -180,13 +181,12 @@ public final class Socks5TestProxy { wait(5000); } catch (InterruptedException e) { LOGGER.log(Level.SEVERE, "exception", e); - } finally { - if (!startupComplete) { - throw new IllegalStateException("Startup of Socks5TestProxy failed within 5 seconds"); - } } } } + if (!startupComplete) { + throw new IllegalStateException("Startup of Socks5TestProxy failed within 5 seconds"); + } return this.connectionMap.get(digest); } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java index d6ee667d5..1e400ea5a 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java @@ -170,7 +170,7 @@ public class SmackIntegrationTestFramework { return testRunResult; } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "Finally"}) private void runTests(Set> classes) throws NoResponseException, NotConnectedException, InterruptedException { for (Class testClass : classes) { diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/ResultSyncPoint.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/ResultSyncPoint.java index 288372a9b..475f7f059 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/ResultSyncPoint.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/ResultSyncPoint.java @@ -33,7 +33,12 @@ public class ResultSyncPoint { if (exception != null) { throw exception; } - wait(timeout); + final long deadline = System.currentTimeMillis() + timeout; + while (result == null && exception == null) { + final long now = System.currentTimeMillis(); + if (now > deadline) break; + wait(deadline - now); + } } if (result != null) { return result; diff --git a/smack-java7/src/main/java/org/jivesoftware/smack/java7/XmppHostnameVerifier.java b/smack-java7/src/main/java/org/jivesoftware/smack/java7/XmppHostnameVerifier.java index 8ce6d9667..d31431406 100644 --- a/smack-java7/src/main/java/org/jivesoftware/smack/java7/XmppHostnameVerifier.java +++ b/smack-java7/src/main/java/org/jivesoftware/smack/java7/XmppHostnameVerifier.java @@ -184,8 +184,8 @@ public class XmppHostnameVerifier implements HostnameVerifier { } private static boolean matchesPerRfc2818(String name, String template) { - String[] nameParts = name.toLowerCase(Locale.US).split("."); - String[] templateParts = template.toLowerCase(Locale.US).split("."); + String[] nameParts = name.toLowerCase(Locale.US).split("\\."); + String[] templateParts = template.toLowerCase(Locale.US).split("\\."); if (nameParts.length != templateParts.length) { return false; diff --git a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/JingleManager.java b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/JingleManager.java index 683aa1a9e..f0c214983 100644 --- a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/JingleManager.java +++ b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/JingleManager.java @@ -183,6 +183,7 @@ import org.jxmpp.jid.Jid; * @see JingleMediaManager * @see BasicTransportManager , STUNTransportManager, BridgedTransportManager, TransportResolver, BridgedResolver, ICEResolver, STUNResolver and BasicResolver. */ +@SuppressWarnings("SynchronizeOnNonFinalField") public class JingleManager implements JingleSessionListener { private static final Logger LOGGER = Logger.getLogger(JingleManager.class.getName()); diff --git a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/mediaimpl/jmf/AudioReceiver.java b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/mediaimpl/jmf/AudioReceiver.java index a82b3633b..d19a3bb9f 100644 --- a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/mediaimpl/jmf/AudioReceiver.java +++ b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/mediaimpl/jmf/AudioReceiver.java @@ -51,7 +51,7 @@ public class AudioReceiver implements ReceiveStreamListener, SessionListener, boolean dataReceived = false; - Object dataSync; + final Object dataSync; JingleMediaSession jingleMediaSession; public AudioReceiver(final Object dataSync, final JingleMediaSession jingleMediaSession) { diff --git a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/nat/TransportCandidate.java b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/nat/TransportCandidate.java index d179b0669..9f5d4a94c 100644 --- a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/nat/TransportCandidate.java +++ b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/nat/TransportCandidate.java @@ -44,6 +44,7 @@ import org.jxmpp.jid.Jid; * @author Thiago Camargo * @author Alvaro Saurin */ +@SuppressWarnings("EqualsHashCode") public abstract class TransportCandidate { private static final Logger LOGGER = Logger.getLogger(TransportCandidate.class.getName()); diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java index 34a89a589..02eaa8a31 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java @@ -55,8 +55,8 @@ public class AgentRoster { private XMPPConnection connection; private Jid workgroupJID; - private List entries; - private List listeners; + private final List entries = new ArrayList(); + private final List listeners = new ArrayList<>(); private final Map> presenceMap = new HashMap<>(); // The roster is marked as initialized when at least a single roster packet // has been recieved and processed. @@ -72,8 +72,6 @@ public class AgentRoster { AgentRoster(XMPPConnection connection, Jid workgroupJID) throws NotConnectedException, InterruptedException { this.connection = connection; this.workgroupJID = workgroupJID; - entries = new ArrayList(); - listeners = new ArrayList(); // Listen for any roster packets. StanzaFilter rosterFilter = new StanzaTypeFilter(AgentStatusRequest.class); connection.addAsyncStanzaListener(new AgentStatusListener(), rosterFilter); diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java index 1606bfd29..447d5a98b 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java @@ -47,11 +47,10 @@ public class AgentStatusRequest extends IQ { */ public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; - private Set agents; + private final Set agents = new HashSet<>(); public AgentStatusRequest() { super(ELEMENT_NAME, NAMESPACE); - agents = new HashSet(); } public int getAgentCount() { diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java index 7b0b35f60..4ca89b014 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java @@ -132,7 +132,7 @@ public class OccupantsInfo extends IQ { public static class Provider extends IQProvider { @Override - public OccupantsInfo parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException { + public OccupantsInfo parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException { OccupantsInfo occupantsInfo = new OccupantsInfo(parser.getAttributeValue("", "roomID")); boolean done = false; @@ -149,7 +149,7 @@ public class OccupantsInfo extends IQ { return occupantsInfo; } - private OccupantInfo parseOccupantInfo(XmlPullParser parser) throws XmlPullParserException, IOException { + private OccupantInfo parseOccupantInfo(XmlPullParser parser) throws XmlPullParserException, IOException, SmackException { boolean done = false; String jid = null; @@ -167,7 +167,7 @@ public class OccupantsInfo extends IQ { try { joined = UTC_FORMAT.parse(parser.nextText()); } catch (ParseException e) { - new SmackException(e); + throw new SmackException(e); } } else if (eventType == XmlPullParser.END_TAG && "occupant".equals(parser.getName())) { diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java index 341d54f96..3c3edfc1c 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java @@ -56,14 +56,7 @@ public final class QueueDetails implements ExtensionElement { /** * The list of users in the queue. */ - private Set users; - - /** - * Creates a new QueueDetails packet - */ - private QueueDetails() { - users = new HashSet(); - } + private final Set users = new HashSet<>(); /** * Returns the number of users currently in the queue that are waiting to diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/xroster/packet/RosterExchange.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/xroster/packet/RosterExchange.java index 33709768b..3cec05ebd 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/xroster/packet/RosterExchange.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/xroster/packet/RosterExchange.java @@ -50,7 +50,7 @@ import java.util.List; */ public class RosterExchange implements ExtensionElement { - private List remoteRosterEntries = new ArrayList(); + private final List remoteRosterEntries = new ArrayList<>(); /** * Creates a new empty roster exchange package. diff --git a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java index bdd0690ac..211c0988c 100644 --- a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java +++ b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java @@ -105,6 +105,7 @@ public class PacketWriterTest { public class BlockingStringWriter extends Writer { @Override + @SuppressWarnings("WaitNotInLoop") public void write(char[] cbuf, int off, int len) throws IOException { try { wait();