View Javadoc
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 }