001/*
002 * Copyright 2024-2025 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet.servlet.jakarta;
018
019import javax.annotation.Nonnull;
020import javax.annotation.Nullable;
021import javax.annotation.concurrent.NotThreadSafe;
022import java.io.PrintWriter;
023import java.io.Writer;
024import java.util.Locale;
025import java.util.function.Consumer;
026
027import static java.util.Objects.requireNonNull;
028
029/**
030 * Soklet integration implementation of {@link SokletServletPrintWriter}.
031 *
032 * @author <a href="https://www.revetkn.com">Mark Allen</a>
033 */
034@NotThreadSafe
035public final class SokletServletPrintWriter extends PrintWriter {
036        @Nonnull
037        private final Consumer<SokletServletPrintWriter> writeOccurredCallback;
038        @Nonnull
039        private final Consumer<SokletServletPrintWriter> writeFinalizedCallback;
040        @Nonnull
041        private Boolean writeFinalized = false;
042
043        @Nonnull
044        public static SokletServletPrintWriter withWriter(@Nonnull Writer writer) {
045                return new Builder(writer).build();
046        }
047
048        @Nonnull
049        public static Builder builderWithWriter(@Nonnull Writer writer) {
050                return new Builder(writer);
051        }
052
053        private SokletServletPrintWriter(@Nonnull Builder builder) {
054                super(requireNonNull(builder.writer), true);
055                this.writeOccurredCallback = builder.writeOccurredCallback != null ? builder.writeOccurredCallback : (ignored) -> {};
056                this.writeFinalizedCallback = builder.writeFinalizedCallback != null ? builder.writeFinalizedCallback : (ignored) -> {};
057        }
058
059        /**
060         * Builder used to construct instances of {@link SokletServletPrintWriter}.
061         * <p>
062         * This class is intended for use by a single thread.
063         *
064         * @author <a href="https://www.revetkn.com">Mark Allen</a>
065         */
066        @NotThreadSafe
067        public static class Builder {
068                @Nonnull
069                private Writer writer;
070                @Nullable
071                private Consumer<SokletServletPrintWriter> writeOccurredCallback;
072                @Nullable
073                private Consumer<SokletServletPrintWriter> writeFinalizedCallback;
074
075                @Nonnull
076                private Builder(@Nonnull Writer writer) {
077                        requireNonNull(writer);
078                        this.writer = writer;
079                }
080
081                @Nonnull
082                public Builder writer(@Nonnull Writer writer) {
083                        requireNonNull(writer);
084                        this.writer = writer;
085                        return this;
086                }
087
088                @Nonnull
089                public Builder writeOccurredCallback(@Nullable Consumer<SokletServletPrintWriter> writeOccurredCallback) {
090                        this.writeOccurredCallback = writeOccurredCallback;
091                        return this;
092                }
093
094                @Nonnull
095                public Builder writeFinalizedCallback(@Nullable Consumer<SokletServletPrintWriter> writeFinalizedCallback) {
096                        this.writeFinalizedCallback = writeFinalizedCallback;
097                        return this;
098                }
099
100                @Nonnull
101                public SokletServletPrintWriter build() {
102                        return new SokletServletPrintWriter(this);
103                }
104        }
105
106        @Nonnull
107        protected Boolean getWriteFinalized() {
108                return this.writeFinalized;
109        }
110
111        protected void setWriteFinalized(@Nonnull Boolean writeFinalized) {
112                requireNonNull(writeFinalized);
113                this.writeFinalized = writeFinalized;
114        }
115
116        @Nonnull
117        protected Consumer<SokletServletPrintWriter> getWriteOccurredCallback() {
118                return this.writeOccurredCallback;
119        }
120
121        @Nonnull
122        protected Consumer<SokletServletPrintWriter> getWriteFinalizedCallback() {
123                return this.writeFinalizedCallback;
124        }
125
126// Implementation of PrintWriter methods below:
127
128        @Override
129        public void write(@Nonnull char[] buf,
130                                                                                int off,
131                                                                                int len) {
132                requireNonNull(buf);
133
134                super.write(buf, off, len);
135                super.flush();
136                getWriteOccurredCallback().accept(this);
137        }
138
139        @Override
140        public void write(@Nonnull String s,
141                                                                                int off,
142                                                                                int len) {
143                requireNonNull(s);
144
145                super.write(s, off, len);
146                super.flush();
147                getWriteOccurredCallback().accept(this);
148        }
149
150        @Override
151        public void write(int c) {
152                super.write(c);
153                super.flush();
154                getWriteOccurredCallback().accept(this);
155        }
156
157        @Override
158        public void write(@Nonnull char[] buf) {
159                requireNonNull(buf);
160
161                super.write(buf);
162                super.flush();
163                getWriteOccurredCallback().accept(this);
164        }
165
166        @Override
167        public void write(@Nonnull String s) {
168                requireNonNull(s);
169
170                super.write(s);
171                super.flush();
172                getWriteOccurredCallback().accept(this);
173        }
174
175        @Override
176        public void print(boolean b) {
177                super.print(b);
178                super.flush();
179                getWriteOccurredCallback().accept(this);
180        }
181
182        @Override
183        public void print(char c) {
184                super.print(c);
185                super.flush();
186                getWriteOccurredCallback().accept(this);
187        }
188
189        @Override
190        public void print(int i) {
191                super.print(i);
192                super.flush();
193                getWriteOccurredCallback().accept(this);
194        }
195
196        @Override
197        public void print(long l) {
198                super.print(l);
199                super.flush();
200                getWriteOccurredCallback().accept(this);
201        }
202
203        @Override
204        public void print(float f) {
205                super.print(f);
206                super.flush();
207                getWriteOccurredCallback().accept(this);
208        }
209
210        @Override
211        public void print(double d) {
212                super.print(d);
213                super.flush();
214                getWriteOccurredCallback().accept(this);
215        }
216
217        @Override
218        public void print(@Nonnull char[] s) {
219                requireNonNull(s);
220
221                super.print(s);
222                super.flush();
223                getWriteOccurredCallback().accept(this);
224        }
225
226        @Override
227        public void print(@Nullable String s) {
228                super.print(s);
229                super.flush();
230                getWriteOccurredCallback().accept(this);
231        }
232
233        @Override
234        public void print(@Nullable Object obj) {
235                super.print(obj);
236                super.flush();
237                getWriteOccurredCallback().accept(this);
238        }
239
240        @Override
241        public void println() {
242                super.println();
243                super.flush();
244                getWriteOccurredCallback().accept(this);
245        }
246
247        @Override
248        public void println(boolean x) {
249                super.println(x);
250                super.flush();
251                getWriteOccurredCallback().accept(this);
252        }
253
254        @Override
255        public void println(char x) {
256                super.println(x);
257                super.flush();
258                getWriteOccurredCallback().accept(this);
259        }
260
261        @Override
262        public void println(int x) {
263                super.println(x);
264                super.flush();
265                getWriteOccurredCallback().accept(this);
266        }
267
268        @Override
269        public void println(long x) {
270                super.println(x);
271                super.flush();
272                getWriteOccurredCallback().accept(this);
273        }
274
275        @Override
276        public void println(float x) {
277                super.println(x);
278                super.flush();
279                getWriteOccurredCallback().accept(this);
280        }
281
282        @Override
283        public void println(double x) {
284                super.println(x);
285                super.flush();
286                getWriteOccurredCallback().accept(this);
287        }
288
289        @Override
290        public void println(char[] x) {
291                requireNonNull(x);
292
293                super.println(x);
294                super.flush();
295                getWriteOccurredCallback().accept(this);
296        }
297
298        @Override
299        public void println(@Nullable String x) {
300                super.println(x);
301                super.flush();
302                getWriteOccurredCallback().accept(this);
303        }
304
305        @Override
306        public void println(@Nullable Object x) {
307                super.println(x);
308                super.flush();
309                getWriteOccurredCallback().accept(this);
310        }
311
312        @Override
313        @Nonnull
314        public PrintWriter printf(@Nonnull String format,
315                                                                                                                @Nullable Object... args) {
316                requireNonNull(format);
317
318                PrintWriter printWriter = super.printf(format, args);
319                super.flush();
320                getWriteOccurredCallback().accept(this);
321                return printWriter;
322        }
323
324        @Override
325        @Nonnull
326        public PrintWriter printf(@Nullable Locale l,
327                                                                                                                @Nonnull String format,
328                                                                                                                @Nullable Object... args) {
329                requireNonNull(format);
330
331                PrintWriter printWriter = super.printf(l, format, args);
332                super.flush();
333                getWriteOccurredCallback().accept(this);
334                return printWriter;
335        }
336
337        @Override
338        @Nonnull
339        public PrintWriter format(@Nonnull String format,
340                                                                                                                @Nullable Object... args) {
341                requireNonNull(format);
342
343                PrintWriter printWriter = super.format(format, args);
344                super.flush();
345                getWriteOccurredCallback().accept(this);
346                return printWriter;
347        }
348
349        @Override
350        @Nonnull
351        public PrintWriter format(@Nullable Locale l,
352                                                                                                                @Nonnull String format,
353                                                                                                                @Nullable Object... args) {
354                requireNonNull(format);
355
356                PrintWriter printWriter = super.format(l, format, args);
357                super.flush();
358                getWriteOccurredCallback().accept(this);
359                return printWriter;
360        }
361
362        @Override
363        @Nonnull
364        public PrintWriter append(@Nullable CharSequence csq) {
365                PrintWriter printWriter = super.append(csq);
366                super.flush();
367                getWriteOccurredCallback().accept(this);
368                return printWriter;
369        }
370
371        @Override
372        @Nonnull
373        public PrintWriter append(@Nullable CharSequence csq,
374                                                                                                                int start,
375                                                                                                                int end) {
376                PrintWriter printWriter = super.append(csq, start, end);
377                super.flush();
378                getWriteOccurredCallback().accept(this);
379                return printWriter;
380        }
381
382        @Override
383        @Nonnull
384        public PrintWriter append(char c) {
385                PrintWriter printWriter = super.append(c);
386                super.flush();
387                getWriteOccurredCallback().accept(this);
388                return printWriter;
389        }
390
391        @Override
392        public void flush() {
393                super.flush();
394
395                if (!getWriteFinalized()) {
396                        setWriteFinalized(true);
397                        getWriteFinalizedCallback().accept(this);
398                }
399        }
400
401        @Override
402        public void close() {
403                super.flush();
404                super.close();
405
406                if (!getWriteFinalized()) {
407                        setWriteFinalized(true);
408                        getWriteFinalizedCallback().accept(this);
409                }
410        }
411}