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