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.javax;
018
019import com.soklet.servlet.javax.SokletServletPrintWriterEvent.CharAppended;
020import com.soklet.servlet.javax.SokletServletPrintWriterEvent.CharSequenceAppended;
021import com.soklet.servlet.javax.SokletServletPrintWriterEvent.CharWritten;
022import com.soklet.servlet.javax.SokletServletPrintWriterEvent.CharsWritten;
023import com.soklet.servlet.javax.SokletServletPrintWriterEvent.FormatPerformed;
024import com.soklet.servlet.javax.SokletServletPrintWriterEvent.NewlinePrinted;
025import com.soklet.servlet.javax.SokletServletPrintWriterEvent.PrintfPerformed;
026import com.soklet.servlet.javax.SokletServletPrintWriterEvent.StringWritten;
027import com.soklet.servlet.javax.SokletServletPrintWriterEvent.ValuePrinted;
028import com.soklet.servlet.javax.SokletServletPrintWriterEvent.ValueWithNewlinePrinted;
029
030import javax.annotation.Nonnull;
031import javax.annotation.Nullable;
032import javax.annotation.concurrent.NotThreadSafe;
033import java.io.PrintWriter;
034import java.io.Writer;
035import java.util.Locale;
036import java.util.function.BiConsumer;
037import java.util.function.Consumer;
038
039import static java.util.Objects.requireNonNull;
040
041/**
042 * Special implementation of {@link PrintWriter}, designed for use with {@link SokletHttpServletResponse}.
043 *
044 * @author <a href="https://www.revetkn.com">Mark Allen</a>
045 */
046@NotThreadSafe
047public final class SokletServletPrintWriter extends PrintWriter {
048        @Nonnull
049        private final BiConsumer<SokletServletPrintWriter, SokletServletPrintWriterEvent> onWriteOccurred;
050        @Nonnull
051        private final Consumer<SokletServletPrintWriter> onWriteFinalized;
052        @Nonnull
053        private Boolean writeFinalized = false;
054
055        @Nonnull
056        public static Builder withWriter(@Nonnull Writer writer) {
057                return new Builder(writer);
058        }
059
060        private SokletServletPrintWriter(@Nonnull Builder builder) {
061                super(requireNonNull(builder.writer), true);
062                this.onWriteOccurred = builder.onWriteOccurred != null ? builder.onWriteOccurred : (ignored1, ignored2) -> {};
063                this.onWriteFinalized = builder.onWriteFinalized != null ? builder.onWriteFinalized : (ignored) -> {};
064        }
065
066        /**
067         * Builder used to construct instances of {@link SokletServletPrintWriter}.
068         * <p>
069         * This class is intended for use by a single thread.
070         *
071         * @author <a href="https://www.revetkn.com">Mark Allen</a>
072         */
073        @NotThreadSafe
074        public static class Builder {
075                @Nonnull
076                private Writer writer;
077                @Nullable
078                private BiConsumer<SokletServletPrintWriter, SokletServletPrintWriterEvent> onWriteOccurred;
079                @Nullable
080                private Consumer<SokletServletPrintWriter> onWriteFinalized;
081
082                @Nonnull
083                private Builder(@Nonnull Writer writer) {
084                        requireNonNull(writer);
085                        this.writer = writer;
086                }
087
088                @Nonnull
089                public Builder writer(@Nonnull Writer writer) {
090                        requireNonNull(writer);
091                        this.writer = writer;
092                        return this;
093                }
094
095                @Nonnull
096                public Builder onWriteOccurred(@Nullable BiConsumer<SokletServletPrintWriter, SokletServletPrintWriterEvent> onWriteOccurred) {
097                        this.onWriteOccurred = onWriteOccurred;
098                        return this;
099                }
100
101                @Nonnull
102                public Builder onWriteFinalized(@Nullable Consumer<SokletServletPrintWriter> onWriteFinalized) {
103                        this.onWriteFinalized = onWriteFinalized;
104                        return this;
105                }
106
107                @Nonnull
108                public SokletServletPrintWriter build() {
109                        return new SokletServletPrintWriter(this);
110                }
111        }
112
113        @Nonnull
114        protected Boolean getWriteFinalized() {
115                return this.writeFinalized;
116        }
117
118        protected void setWriteFinalized(@Nonnull Boolean writeFinalized) {
119                requireNonNull(writeFinalized);
120                this.writeFinalized = writeFinalized;
121        }
122
123        @Nonnull
124        protected BiConsumer<SokletServletPrintWriter, SokletServletPrintWriterEvent> getOnWriteOccurred() {
125                return this.onWriteOccurred;
126        }
127
128        @Nonnull
129        protected Consumer<SokletServletPrintWriter> getOnWriteFinalized() {
130                return this.onWriteFinalized;
131        }
132
133// Implementation of PrintWriter methods below:
134
135        @Override
136        public void write(@Nonnull char[] buf,
137                                                                                int off,
138                                                                                int len) {
139                requireNonNull(buf);
140
141                super.write(buf, off, len);
142                super.flush();
143                getOnWriteOccurred().accept(this, new CharsWritten(buf, off, len));
144        }
145
146        @Override
147        public void write(@Nonnull String s,
148                                                                                int off,
149                                                                                int len) {
150                requireNonNull(s);
151
152                super.write(s, off, len);
153                super.flush();
154                getOnWriteOccurred().accept(this, new StringWritten(s, off, len));
155        }
156
157        @Override
158        public void write(int c) {
159                super.write(c);
160                super.flush();
161                getOnWriteOccurred().accept(this, new CharWritten(c));
162        }
163
164        @Override
165        public void write(@Nonnull char[] buf) {
166                requireNonNull(buf);
167
168                super.write(buf);
169                super.flush();
170                getOnWriteOccurred().accept(this, new CharsWritten(buf, 0, buf.length));
171        }
172
173        @Override
174        public void write(@Nonnull String s) {
175                requireNonNull(s);
176
177                super.write(s);
178                super.flush();
179                getOnWriteOccurred().accept(this, new StringWritten(s, 0, s.length()));
180        }
181
182        @Override
183        public void print(boolean b) {
184                super.print(b);
185                super.flush();
186                getOnWriteOccurred().accept(this, new ValuePrinted(b));
187        }
188
189        @Override
190        public void print(char c) {
191                super.print(c);
192                super.flush();
193                getOnWriteOccurred().accept(this, new ValuePrinted(c));
194        }
195
196        @Override
197        public void print(int i) {
198                super.print(i);
199                super.flush();
200                getOnWriteOccurred().accept(this, new ValuePrinted(i));
201        }
202
203        @Override
204        public void print(long l) {
205                super.print(l);
206                super.flush();
207                getOnWriteOccurred().accept(this, new ValuePrinted(l));
208        }
209
210        @Override
211        public void print(float f) {
212                super.print(f);
213                super.flush();
214                getOnWriteOccurred().accept(this, new ValuePrinted(f));
215        }
216
217        @Override
218        public void print(double d) {
219                super.print(d);
220                super.flush();
221                getOnWriteOccurred().accept(this, new ValuePrinted(d));
222        }
223
224        @Override
225        public void print(@Nonnull char[] s) {
226                requireNonNull(s);
227
228                super.print(s);
229                super.flush();
230                getOnWriteOccurred().accept(this, new ValuePrinted(s));
231        }
232
233        @Override
234        public void print(@Nullable String s) {
235                super.print(s);
236                super.flush();
237                getOnWriteOccurred().accept(this, new ValuePrinted(s));
238        }
239
240        @Override
241        public void print(@Nullable Object obj) {
242                super.print(obj);
243                super.flush();
244                getOnWriteOccurred().accept(this, new ValuePrinted(obj));
245        }
246
247        @Override
248        public void println() {
249                super.println();
250                super.flush();
251                getOnWriteOccurred().accept(this, new NewlinePrinted());
252        }
253
254        @Override
255        public void println(boolean x) {
256                super.println(x);
257                super.flush();
258                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
259        }
260
261        @Override
262        public void println(char x) {
263                super.println(x);
264                super.flush();
265                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
266        }
267
268        @Override
269        public void println(int x) {
270                super.println(x);
271                super.flush();
272                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
273        }
274
275        @Override
276        public void println(long x) {
277                super.println(x);
278                super.flush();
279                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
280        }
281
282        @Override
283        public void println(float x) {
284                super.println(x);
285                super.flush();
286                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
287        }
288
289        @Override
290        public void println(double x) {
291                super.println(x);
292                super.flush();
293                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
294        }
295
296        @Override
297        public void println(char[] x) {
298                requireNonNull(x);
299
300                super.println(x);
301                super.flush();
302                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
303        }
304
305        @Override
306        public void println(@Nullable String x) {
307                super.println(x);
308                super.flush();
309                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
310        }
311
312        @Override
313        public void println(@Nullable Object x) {
314                super.println(x);
315                super.flush();
316                getOnWriteOccurred().accept(this, new ValueWithNewlinePrinted(x));
317        }
318
319        @Override
320        @Nonnull
321        public PrintWriter printf(@Nonnull String format,
322                                                                                                                @Nullable Object... args) {
323                requireNonNull(format);
324
325                PrintWriter printWriter = super.printf(format, args);
326                super.flush();
327
328                Object[] normalizedArgs = args != null ? args : new Object[0];
329                getOnWriteOccurred().accept(this, new PrintfPerformed(null, format, normalizedArgs));
330
331                return printWriter;
332        }
333
334        @Override
335        @Nonnull
336        public PrintWriter printf(@Nullable Locale l,
337                                                                                                                @Nonnull String format,
338                                                                                                                @Nullable Object... args) {
339                requireNonNull(format);
340
341                PrintWriter printWriter = super.printf(l, format, args);
342                super.flush();
343                getOnWriteOccurred().accept(this, new PrintfPerformed(l, format, args));
344                return printWriter;
345        }
346
347        @Override
348        @Nonnull
349        public PrintWriter format(@Nonnull String format,
350                                                                                                                @Nullable Object... args) {
351                requireNonNull(format);
352
353                PrintWriter printWriter = super.format(format, args);
354                super.flush();
355
356                Object[] normalizedArgs = args != null ? args : new Object[0];
357                getOnWriteOccurred().accept(this, new FormatPerformed(null, format, normalizedArgs));
358
359                return printWriter;
360        }
361
362        @Override
363        @Nonnull
364        public PrintWriter format(@Nullable Locale l,
365                                                                                                                @Nonnull String format,
366                                                                                                                @Nullable Object... args) {
367                requireNonNull(format);
368
369                PrintWriter printWriter = super.format(l, format, args);
370                super.flush();
371
372                Object[] normalizedArgs = args != null ? args : new Object[0];
373                getOnWriteOccurred().accept(this, new FormatPerformed(l, format, normalizedArgs));
374
375                return printWriter;
376        }
377
378        @Override
379        @Nonnull
380        public PrintWriter append(@Nullable CharSequence csq) {
381                // JDK does this, we mirror it
382                if (csq == null)
383                        csq = "null";
384
385                PrintWriter printWriter = super.append(csq);
386                super.flush();
387                getOnWriteOccurred().accept(this, new CharSequenceAppended(csq, 0, csq.length()));
388                return printWriter;
389        }
390
391        @Override
392        @Nonnull
393        public PrintWriter append(@Nullable CharSequence csq,
394                                                                                                                int start,
395                                                                                                                int end) {
396                // JDK does this, we mirror it
397                if (csq == null)
398                        csq = "null";
399
400                PrintWriter printWriter = super.append(csq, start, end);
401                super.flush();
402                getOnWriteOccurred().accept(this, new CharSequenceAppended(csq, start, end));
403                return printWriter;
404        }
405
406        @Override
407        @Nonnull
408        public PrintWriter append(char c) {
409                PrintWriter printWriter = super.append(c);
410                super.flush();
411                getOnWriteOccurred().accept(this, new CharAppended(c));
412                return printWriter;
413        }
414
415        @Override
416        public void flush() {
417                super.flush();
418
419                if (!getWriteFinalized()) {
420                        setWriteFinalized(true);
421                        getOnWriteFinalized().accept(this);
422                }
423        }
424
425        @Override
426        public void close() {
427                super.flush();
428                super.close();
429
430                if (!getWriteFinalized()) {
431                        setWriteFinalized(true);
432                        getOnWriteFinalized().accept(this);
433                }
434        }
435}