1 /*
2 * Copyright 2012-2025 CodeLibs Project and the Others.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 * either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
15 */
16 package org.codelibs.fess.util;
17
18 import java.io.BufferedReader;
19 import java.io.InputStream;
20 import java.io.InputStreamReader;
21 import java.nio.charset.Charset;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.function.Consumer;
25
26 import org.apache.logging.log4j.LogManager;
27 import org.apache.logging.log4j.Logger;
28
29 /**
30 * A thread that reads from an input stream line by line and maintains a buffer of recent lines.
31 * This class provides functionality to read input stream data asynchronously,
32 * optionally process each line with a callback function, and maintain a circular buffer
33 * of recent lines for retrieval.
34 */
35 public class InputStreamThread extends Thread {
36 /** Logger instance for this class */
37 private static final Logger logger = LogManager.getLogger(InputStreamThread.class);
38
39 /** Buffered reader for reading from the input stream */
40 private final BufferedReader br;
41
42 /** Maximum buffer size constant */
43 public static final int MAX_BUFFER_SIZE = 1000;
44
45 /** List storing recent lines from the input stream */
46 private final List<String> list = new LinkedList<>();
47
48 /** Maximum number of lines to keep in the buffer */
49 private final int bufferSize;
50
51 /** Callback function to process each line as it's read */
52 private final Consumer<String> outputCallback;
53
54 /**
55 * Creates a new input stream thread.
56 *
57 * @param is the input stream to read from
58 * @param charset the character encoding to use for reading
59 * @param bufferSize the maximum number of lines to keep in the buffer (0 to disable buffering)
60 * @param outputCallback optional callback function to process each line (can be null)
61 */
62 public InputStreamThread(final InputStream is, final Charset charset, final int bufferSize, final Consumer<String> outputCallback) {
63 super("InputStreamThread");
64 this.bufferSize = bufferSize;
65 this.outputCallback = outputCallback;
66
67 br = new BufferedReader(new InputStreamReader(is, charset));
68 }
69
70 /**
71 * Runs the thread to continuously read lines from the input stream.
72 * Each line is processed by the output callback (if provided) and added to the buffer.
73 * The buffer is maintained as a circular buffer with the specified size.
74 */
75 @Override
76 public void run() {
77 boolean running = true;
78 while (running) {
79 try {
80 final String line = br.readLine();
81 if (line == null) {
82 running = false;
83 } else {
84 if (logger.isDebugEnabled()) {
85 logger.debug(line);
86 }
87 if (bufferSize > 0) {
88 list.add(line);
89 }
90 if (outputCallback != null) {
91 outputCallback.accept(line);
92 }
93 if (list.size() > bufferSize) {
94 list.remove(0);
95 }
96 }
97 } catch (final Exception e) {
98 running = false;
99 if (logger.isDebugEnabled()) {
100 logger.debug("Failed to process an input stream.", e);
101 }
102 }
103 }
104 }
105
106 /**
107 * Returns all buffered lines as a single string, separated by newlines.
108 *
109 * @return the concatenated output of all buffered lines
110 */
111 public String getOutput() {
112 final StringBuilder buf = new StringBuilder(100);
113 for (final String value : list) {
114 buf.append(value).append("\n");
115 }
116 return buf.toString();
117 }
118
119 /**
120 * Checks if the buffer contains a line that matches the specified value (after trimming).
121 *
122 * @param value the value to search for in the buffered lines
123 * @return true if a matching line is found, false otherwise
124 */
125 public boolean contains(final String value) {
126 for (final String line : list) {
127 if (line.trim().equals(value)) {
128 return true;
129 }
130 }
131 return false;
132 }
133 }