All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.util.IOHelper Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * IO helper class.
 */
public final class IOHelper {

    public static Supplier defaultCharset = Charset::defaultCharset;

    // Use the same default buffer size as the JVM
    public static final int DEFAULT_BUFFER_SIZE = 16384;

    public static final long INITIAL_OFFSET = 0;

    private static final Logger LOG = LoggerFactory.getLogger(IOHelper.class);

    // allows to turn on backwards compatible to turn off regarding the first
    // read byte with value zero (0b0) as EOL.
    // See more at CAMEL-11672
    private static final boolean ZERO_BYTE_EOL_ENABLED
            = "true".equalsIgnoreCase(System.getProperty("camel.zeroByteEOLEnabled", "true"));

    private IOHelper() {
        // Utility Class
    }

    /**
     * Wraps the passed in into a {@link BufferedInputStream} object and returns that. If the passed
     * in is already an instance of {@link BufferedInputStream} returns the same passed in
     * reference as is (avoiding double wrapping).
     *
     * @param  in the wrapee to be used for the buffering support
     * @return    the passed in decorated through a {@link BufferedInputStream} object as wrapper
     */
    public static BufferedInputStream buffered(InputStream in) {
        return (in instanceof BufferedInputStream bi) ? bi : new BufferedInputStream(in);
    }

    /**
     * Wraps the passed out into a {@link BufferedOutputStream} object and returns that. If the passed
     * out is already an instance of {@link BufferedOutputStream} returns the same passed out
     * reference as is (avoiding double wrapping).
     *
     * @param  out the wrapee to be used for the buffering support
     * @return     the passed out decorated through a {@link BufferedOutputStream} object as wrapper
     */
    public static BufferedOutputStream buffered(OutputStream out) {
        return (out instanceof BufferedOutputStream bo) ? bo : new BufferedOutputStream(out);
    }

    /**
     * Wraps the passed reader into a {@link BufferedReader} object and returns that. If the passed
     * reader is already an instance of {@link BufferedReader} returns the same passed reader
     * reference as is (avoiding double wrapping).
     *
     * @param  reader the wrapee to be used for the buffering support
     * @return        the passed reader decorated through a {@link BufferedReader} object as wrapper
     */
    public static BufferedReader buffered(Reader reader) {
        return (reader instanceof BufferedReader br) ? br : new BufferedReader(reader);
    }

    /**
     * Wraps the passed writer into a {@link BufferedWriter} object and returns that. If the passed
     * writer is already an instance of {@link BufferedWriter} returns the same passed writer
     * reference as is (avoiding double wrapping).
     *
     * @param  writer the writer to be used for the buffering support
     * @return        the passed writer decorated through a {@link BufferedWriter} object as wrapper
     */
    public static BufferedWriter buffered(Writer writer) {
        return (writer instanceof BufferedWriter bw) ? bw : new BufferedWriter(writer);
    }

    public static String toString(Reader reader) throws IOException {
        return toString(reader, INITIAL_OFFSET);
    }

    public static String toString(Reader reader, long offset) throws IOException {
        return toString(buffered(reader), offset);
    }

    public static String toString(BufferedReader reader) throws IOException {
        return toString(reader, INITIAL_OFFSET);
    }

    public static String toString(BufferedReader reader, long offset) throws IOException {
        StringBuilder sb = new StringBuilder(1024);

        reader.skip(offset);

        char[] buf = new char[1024];
        try {
            int len;
            // read until we reach then end which is the -1 marker
            while ((len = reader.read(buf)) != -1) {
                sb.append(buf, 0, len);
            }
        } finally {
            IOHelper.close(reader, "reader", LOG);
        }

        return sb.toString();
    }

    /**
     * Copies the data from the input stream to the output stream. Uses {@link InputStream#transferTo(OutputStream)}.
     *
     * @param  input       the input stream buffer
     * @param  output      the output stream buffer
     * @return             the number of bytes copied
     * @throws IOException for I/O errors
     */
    public static int copy(InputStream input, OutputStream output) throws IOException {
        int copied = (int) input.transferTo(output);
        output.flush();
        return copied;
    }

    /**
     * Copies the data from the input stream to the output stream. Uses the legacy copy logic. Prefer using
     * {@link IOHelper#copy(InputStream, OutputStream)} unless you have to control how data is flushed the buffer
     *
     * @param      input       the input stream buffer
     * @param      output      the output stream buffer
     * @param      bufferSize  the size of the buffer used for the copies
     * @return                 the number of bytes copied
     * @deprecated             Prefer using {@link IOHelper#copy(InputStream, OutputStream)}
     * @throws     IOException for I/O errors
     */
    @Deprecated(since = "4.8.0")
    public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
        return copy(input, output, bufferSize, false);
    }

    /**
     * Copies the data from the input stream to the output stream. Uses the legacy copy logic. Prefer using
     * {@link IOHelper#copy(InputStream, OutputStream)} unless you have to control how data is flushed the buffer
     *
     * @param  input            the input stream buffer
     * @param  output           the output stream buffer
     * @param  bufferSize       the size of the buffer used for the copies
     * @param  flushOnEachWrite whether to flush the data everytime that data is written to the buffer
     * @return                  the number of bytes copied
     * @throws IOException      for I/O errors
     */
    public static int copy(final InputStream input, final OutputStream output, int bufferSize, boolean flushOnEachWrite)
            throws IOException {
        return copy(input, output, bufferSize, flushOnEachWrite, -1);
    }

    /**
     * Copies the data from the input stream to the output stream. Uses the legacy copy logic. Prefer using
     * {@link IOHelper#copy(InputStream, OutputStream)} unless you have to control how data is flushed the buffer
     *
     * @param      input            the input stream buffer
     * @param      output           the output stream buffer
     * @param      bufferSize       the size of the buffer used for the copies
     * @param      flushOnEachWrite whether to flush the data everytime that data is written to the buffer
     * @return                      the number of bytes copied
     * @deprecated                  Prefer using {@link IOHelper#copy(InputStream, OutputStream)}
     * @throws     IOException      for I/O errors
     */
    public static int copy(
            final InputStream input, final OutputStream output, int bufferSize, boolean flushOnEachWrite,
            long maxSize)
            throws IOException {

        if (input instanceof ByteArrayInputStream) {
            // optimized for byte arrays as we only need the max size it can be
            input.mark(0);
            input.reset();
            bufferSize = input.available();
        } else {
            int avail = input.available();
            if (avail > bufferSize) {
                bufferSize = avail;
            }
        }

        if (bufferSize > 262144) {
            // upper cap to avoid buffers too big
            bufferSize = 262144;
        }

        if (LOG.isTraceEnabled()) {
            LOG.trace("Copying InputStream: {} -> OutputStream: {} with buffer: {} and flush on each write {}", input, output,
                    bufferSize, flushOnEachWrite);
        }

        int total = 0;
        final byte[] buffer = new byte[bufferSize];
        int n = input.read(buffer);

        boolean hasData;
        if (ZERO_BYTE_EOL_ENABLED) {
            // workaround issue on some application servers which can return 0
            // (instead of -1)
            // as first byte to indicate the end of stream (CAMEL-11672)
            hasData = n > 0;
        } else {
            hasData = n > -1;
        }
        if (hasData) {
            while (-1 != n) {
                output.write(buffer, 0, n);
                if (flushOnEachWrite) {
                    output.flush();
                }
                total += n;
                if (maxSize > 0 && total > maxSize) {
                    throw new IOException("The InputStream entry being copied exceeds the maximum allowed size");
                }
                n = input.read(buffer);
            }
        }
        if (!flushOnEachWrite) {
            // flush at end, if we didn't do it during the writing
            output.flush();
        }
        return total;
    }

    /**
     * Copies the data from the input stream to the output stream and closes the input stream afterward. Uses
     * {@link InputStream#transferTo(OutputStream)}.
     *
     * @param  input       the input stream buffer
     * @param  output      the output stream buffer
     * @throws IOException
     */
    public static void copyAndCloseInput(InputStream input, OutputStream output) throws IOException {
        copy(input, output);
        close(input, null, LOG);
    }

    /**
     * Copies the data from the input stream to the output stream and closes the input stream afterward. Uses Camel's
     * own copying logic. Prefer using {@link IOHelper#copyAndCloseInput(InputStream, OutputStream)} unless you need a
     * specific buffer size.
     *
     * @param  input       the input stream buffer
     * @param  output      the output stream buffer
     * @param  bufferSize  the size of the buffer used for the copies
     * @throws IOException
     */
    public static void copyAndCloseInput(InputStream input, OutputStream output, int bufferSize) throws IOException {
        copy(input, output, bufferSize);
        close(input, null, LOG);
    }

    public static int copy(final Reader input, final Writer output, int bufferSize) throws IOException {
        final char[] buffer = new char[bufferSize];
        int n = input.read(buffer);
        int total = 0;
        while (-1 != n) {
            output.write(buffer, 0, n);
            total += n;
            n = input.read(buffer);
        }
        output.flush();
        return total;
    }

    public static void transfer(ReadableByteChannel input, WritableByteChannel output) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
        while (input.read(buffer) >= 0) {
            buffer.flip();
            while (buffer.hasRemaining()) {
                output.write(buffer);
            }
            buffer.clear();
        }
    }

    /**
     * Forces any updates to this channel's file to be written to the storage device that contains it.
     *
     * @param channel the file channel
     * @param name    the name of the resource
     * @param log     the log to use when reporting warnings, will use this class's own {@link Logger} if
     *                log == null
     */
    public static void force(FileChannel channel, String name, Logger log) {
        try {
            if (channel != null) {
                channel.force(true);
            }
        } catch (Exception e) {
            if (log == null) {
                // then fallback to use the own Logger
                log = LOG;
            }
            if (name != null) {
                log.debug("Cannot force FileChannel: {}. Reason: {}", name, e.getMessage(), e);
            } else {
                log.debug("Cannot force FileChannel. Reason: {}", e.getMessage(), e);
            }
        }
    }

    /**
     * Forces any updates to a FileOutputStream be written to the storage device that contains it.
     *
     * @param os   the file output stream
     * @param name the name of the resource
     * @param log  the log to use when reporting warnings, will use this class's own {@link Logger} if
     *             log == null
     */
    public static void force(FileOutputStream os, String name, Logger log) {
        try {
            if (os != null) {
                os.getFD().sync();
            }
        } catch (Exception e) {
            if (log == null) {
                // then fallback to use the own Logger
                log = LOG;
            }
            if (name != null) {
                log.debug("Cannot sync FileDescriptor: {}. Reason: {}", name, e.getMessage(), e);
            } else {
                log.debug("Cannot sync FileDescriptor. Reason: {}", e.getMessage(), e);
            }
        }
    }

    /**
     * Closes the given writer, logging any closing exceptions to the given log. An associated FileOutputStream can
     * optionally be forced to disk.
     *
     * @param writer the writer to close
     * @param os     an underlying FileOutputStream that will to be forced to disk according to the force parameter
     * @param name   the name of the resource
     * @param log    the log to use when reporting warnings, will use this class's own {@link Logger} if
     *               log == null
     * @param force  forces the FileOutputStream to disk
     */
    public static void close(Writer writer, FileOutputStream os, String name, Logger log, boolean force) {
        if (writer != null && force) {
            // flush the writer prior to syncing the FD
            try {
                writer.flush();
            } catch (Exception e) {
                if (log == null) {
                    // then fallback to use the own Logger
                    log = LOG;
                }
                if (name != null) {
                    log.debug("Cannot flush Writer: {}. Reason: {}", name, e.getMessage(), e);
                } else {
                    log.debug("Cannot flush Writer. Reason: {}", e.getMessage(), e);
                }
            }
            force(os, name, log);
        }
        close(writer, name, log);
    }

    /**
     * Closes the given resource if it is available, logging any closing exceptions to the given log.
     *
     * @param closeable the object to close
     * @param name      the name of the resource
     * @param log       the log to use when reporting closure warnings, will use this class's own {@link Logger} if
     *                  log == null
     */
    public static void close(Closeable closeable, String name, Logger log) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                if (log == null) {
                    // then fallback to use the own Logger
                    log = LOG;
                }
                if (name != null) {
                    log.debug("Cannot close: {}. Reason: {}", name, e.getMessage(), e);
                } else {
                    log.debug("Cannot close. Reason: {}", e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Closes the given resource if it is available and don't catch the exception
     *
     * @param closeable the object to close
     */
    public static void closeWithException(Closeable closeable) throws IOException {
        if (closeable != null) {
            closeable.close();
        }
    }

    /**
     * Closes the given channel if it is available, logging any closing exceptions to the given log. The file's channel
     * can optionally be forced to disk.
     *
     * @param channel the file channel
     * @param name    the name of the resource
     * @param log     the log to use when reporting warnings, will use this class's own {@link Logger} if
     *                log == null
     * @param force   forces the file channel to disk
     */
    public static void close(FileChannel channel, String name, Logger log, boolean force) {
        if (force) {
            force(channel, name, log);
        }
        close(channel, name, log);
    }

    /**
     * Closes the given resource if it is available.
     *
     * @param closeable the object to close
     * @param name      the name of the resource
     */
    public static void close(Closeable closeable, String name) {
        close(closeable, name, LOG);
    }

    /**
     * Closes the given resource if it is available.
     *
     * @param closeable the object to close
     */
    public static void close(Closeable closeable) {
        close(closeable, null, LOG);
    }

    /**
     * Closes the given resources if they are available.
     *
     * @param closeables the objects to close
     */
    public static void close(Closeable... closeables) {
        for (Closeable closeable : closeables) {
            close(closeable);
        }
    }

    public static void closeIterator(Object it) throws IOException {
        if (it instanceof Closeable closeable) {
            IOHelper.closeWithException(closeable);
        }
        if (it instanceof java.util.Scanner scanner) {
            IOException ioException = scanner.ioException();
            if (ioException != null) {
                throw ioException;
            }
        }
    }

    public static void validateCharset(String charset) throws UnsupportedCharsetException {
        if (charset != null) {
            if (Charset.isSupported(charset)) {
                Charset.forName(charset);
                return;
            }
        }
        throw new UnsupportedCharsetException(charset);
    }

    /**
     * Loads the entire stream into memory as a String and returns it.
     * 

* Notice: This implementation appends a \n as line terminator at the of the text. *

* Warning, don't use for crazy big streams :) */ public static String loadText(InputStream in) throws IOException { StringBuilder builder = new StringBuilder(2048); InputStreamReader isr = new InputStreamReader(in); try { BufferedReader reader = buffered(isr); while (true) { String line = reader.readLine(); if (line != null) { builder.append(line); builder.append("\n"); } else { break; } } return builder.toString(); } finally { close(isr, in); } } /** * Loads the entire stream into memory as a String and returns the given line number. *

* Warning, don't use for crazy big streams :) */ public static String loadTextLine(InputStream in, int lineNumber) throws IOException { int i = 0; InputStreamReader isr = new InputStreamReader(in); try { BufferedReader reader = buffered(isr); while (true) { String line = reader.readLine(); if (line != null) { i++; if (i >= lineNumber) { return line; } } else { break; } } } finally { close(isr, in); } return null; } /** * Appends the text to the file. */ public static void appendText(String text, File file) throws IOException { doWriteText(text, file, true); } /** * Writes the text to the file. */ public static void writeText(String text, File file) throws IOException { doWriteText(text, file, false); } @SuppressWarnings("ResultOfMethodCallIgnored") private static void doWriteText(String text, File file, boolean append) throws IOException { if (!file.exists()) { String path = FileUtil.onlyPath(file.getPath()); if (path != null) { new File(path).mkdirs(); } } writeText(text, new FileOutputStream(file, append)); } /** * Writes the text to the stream. */ public static void writeText(String text, OutputStream os) throws IOException { try { os.write(text.getBytes()); } finally { close(os); } } /** * Get the charset name from the content type string * * @param contentType the content type * @return the charset name, or UTF-8 if no found */ public static String getCharsetNameFromContentType(String contentType) { // try optimized for direct match without using splitting int pos = contentType.indexOf("charset="); if (pos != -1) { // special optimization for utf-8 which is a common charset if (contentType.regionMatches(true, pos + 8, "utf-8", 0, 5)) { return "UTF-8"; } int end = contentType.indexOf(';', pos); String charset; if (end > pos) { charset = contentType.substring(pos + 8, end); } else { charset = contentType.substring(pos + 8); } return normalizeCharset(charset); } String[] values = contentType.split(";"); for (String value : values) { value = value.trim(); // Perform a case insensitive "startsWith" check that works for different locales String prefix = "charset="; if (value.regionMatches(true, 0, prefix, 0, prefix.length())) { // Take the charset name String charset = value.substring(8); return normalizeCharset(charset); } } // use UTF-8 as default return "UTF-8"; } /** * This method will take off the quotes and double quotes of the charset */ public static String normalizeCharset(String charset) { if (charset != null) { boolean trim = false; String answer = charset.trim(); if (answer.startsWith("'") || answer.startsWith("\"")) { answer = answer.substring(1); trim = true; } if (answer.endsWith("'") || answer.endsWith("\"")) { answer = answer.substring(0, answer.length() - 1); trim = true; } return trim ? answer.trim() : answer; } else { return null; } } /** * Lookup the OS environment variable in a safe manner by using upper case keys and underscore instead of dash. * * At first lookup attempt is made without considering camelCase keys. The second lookup is converting camelCase to * underscores. * * For example given an ENV variable in either format: - CAMEL_KAMELET_AWS_S3_SOURCE_BUCKETNAMEORARN=myArn - * CAMEL_KAMELET_AWS_S3_SOURCE_BUCKET_NAME_OR_ARN=myArn * * Then the following keys can look up both ENV formats above: - camel.kamelet.awsS3Source.bucketNameOrArn - * camel.kamelet.aws-s3-source.bucketNameOrArn - camel.kamelet.aws-s3-source.bucket-name-or-arn */ public static String lookupEnvironmentVariable(String key) { // lookup OS env with upper case key String upperKey = key.toUpperCase(); String value = System.getenv(upperKey); if (value == null) { value = System.getenv(normalizeEnvironmentVariable(upperKey)); } if (value == null) { // camelCase keys should use underscore as separator String caseKey = StringHelper.camelCaseToDash(key); value = System.getenv(normalizeEnvironmentVariable(caseKey)); } return value; } /** * Convert given key into an OS environment variable. Uses uppercase keys and converts dashes and dots to * underscores. */ public static String normalizeEnvironmentVariable(String key) { String upperKey = key.toUpperCase(); // some OS do not support dashes in keys, so replace with underscore String normalizedKey = upperKey.replace('-', '_'); // and replace dots with underscores so keys like my.key are // translated to MY_KEY return normalizedKey.replace('.', '_'); } /** * Encoding-aware input stream. */ public static class EncodingInputStream extends InputStream { private final Lock lock = new ReentrantLock(); private final Path file; private final BufferedReader reader; private final Charset defaultStreamCharset; private ByteBuffer bufferBytes; private final CharBuffer bufferedChars = CharBuffer.allocate(4096); public EncodingInputStream(Path file, String charset) throws IOException { this.file = file; reader = toReader(file, charset); defaultStreamCharset = defaultCharset.get(); } @Override public int read() throws IOException { if (bufferBytes == null || bufferBytes.remaining() <= 0) { BufferCaster.cast(bufferedChars).clear(); int len = reader.read(bufferedChars); bufferedChars.flip(); if (len == -1) { return -1; } bufferBytes = defaultStreamCharset.encode(bufferedChars); } return bufferBytes.get() & 0xFF; } @Override public void close() throws IOException { reader.close(); } @Override public void reset() throws IOException { lock.lock(); try { reader.reset(); } finally { lock.unlock(); } } public InputStream toOriginalInputStream() throws IOException { return Files.newInputStream(file); } } /** * Encoding-aware file reader. */ public static class EncodingFileReader extends InputStreamReader { private final FileInputStream in; /** * @param in file to read * @param charset character set to use */ public EncodingFileReader(FileInputStream in, String charset) throws UnsupportedEncodingException { super(in, charset); this.in = in; } /** * @param in file to read * @param charset character set to use */ public EncodingFileReader(FileInputStream in, Charset charset) { super(in, charset); this.in = in; } @Override public void close() throws IOException { try { super.close(); } finally { in.close(); } } } /** * Encoding-aware file writer. */ public static class EncodingFileWriter extends OutputStreamWriter { private final FileOutputStream out; /** * @param out file to write * @param charset character set to use */ public EncodingFileWriter(FileOutputStream out, String charset) throws UnsupportedEncodingException { super(out, charset); this.out = out; } /** * @param out file to write * @param charset character set to use */ public EncodingFileWriter(FileOutputStream out, Charset charset) { super(out, charset); this.out = out; } @Override public void close() throws IOException { try { super.close(); } finally { out.close(); } } } /** * Converts the given {@link File} with the given charset to {@link InputStream} with the JVM default charset * * @param file the file to be converted * @param charset the charset the file is read with * @return the input stream with the JVM default charset */ public static InputStream toInputStream(File file, String charset) throws IOException { return toInputStream(file.toPath(), charset); } /** * Converts the given {@link File} with the given charset to {@link InputStream} with the JVM default charset * * @param file the file to be converted * @param charset the charset the file is read with * @return the input stream with the JVM default charset */ public static InputStream toInputStream(Path file, String charset) throws IOException { if (charset != null) { return new EncodingInputStream(file, charset); } else { return buffered(Files.newInputStream(file)); } } public static BufferedReader toReader(Path file, String charset) throws IOException { return toReader(file, charset != null ? Charset.forName(charset) : null); } public static BufferedReader toReader(File file, String charset) throws IOException { return toReader(file, charset != null ? Charset.forName(charset) : null); } public static BufferedReader toReader(File file, Charset charset) throws IOException { return toReader(file.toPath(), charset); } public static BufferedReader toReader(Path file, Charset charset) throws IOException { if (charset != null) { return Files.newBufferedReader(file, charset); } else { return Files.newBufferedReader(file); } } public static BufferedWriter toWriter(FileOutputStream os, String charset) throws IOException { return IOHelper.buffered(new EncodingFileWriter(os, charset)); } public static BufferedWriter toWriter(FileOutputStream os, Charset charset) { return IOHelper.buffered(new EncodingFileWriter(os, charset)); } /** * Reads the file under the given {@code path}, strips lines starting with {@code commentPrefix} and optionally also * strips blank lines (the ones for which {@link String#isBlank()} returns {@code true}. Normalizes EOL characters * to {@code '\n'}. * * @param path the path of the file to read * @param commentPrefix the leading character sequence of comment lines. * @param stripBlankLines if true {@code true} the lines matching {@link String#isBlank()} will not appear in the * result * @return the filtered content of the file */ public static String stripLineComments(Path path, String commentPrefix, boolean stripBlankLines) { StringBuilder result = new StringBuilder(2048); try (Stream lines = Files.lines(path)) { lines .filter(l -> !stripBlankLines || !l.isBlank()) .filter(line -> !line.startsWith(commentPrefix)) .forEach(line -> result.append(line).append('\n')); } catch (IOException e) { throw new RuntimeException("Cannot read file: " + path, e); } return result.toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy