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}