/* * 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.nifi.processors.hadoop.util; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This class allows the user to define byte-array filters or single-byte filters that will modify the content that is written to the underlying stream. Each filter can be given a maximum number of * replacements that it should perform. */ public class ByteFilteringOutputStream extends FilterOutputStream { private final List<Filter> multiByteFilters = new ArrayList<>(); private final List<Filter> singleByteFilters = new ArrayList<>(); private final OutputStream wrapped; public ByteFilteringOutputStream(final OutputStream toWrap) throws IOException { super(toWrap); this.wrapped = toWrap; } @Override public synchronized void write(byte[] buffer, int offset, int length) throws IOException { for (final Filter filter : multiByteFilters) { if (filter.matches(buffer, offset, length)) { wrapped.write(filter.replaceWith); } else { wrapped.write(buffer, offset, length); } } } @Override public synchronized void write(int data) throws IOException { for (final Filter filter : singleByteFilters) { if (filter.matches((byte) data)) { wrapped.write(filter.replaceWith); } else { wrapped.write(data); } } } /** * Causes this stream to write <tt>replaceWith</tt> in place of * <tt>toReplace</tt> if {@link #write(byte[], int, int)} is called where the value to write is equal to * <tt>toReplace</tt>. * <p/> * @param toReplace the byte array to replace * @param replaceWith the byte array to be substituted */ public void addFilter(final byte[] toReplace, final byte[] replaceWith) { addFilter(toReplace, replaceWith, -1); } /** * Causes this stream to write <tt>replaceWith</tt> in place of * <tt>toReplace</tt> if {@link #write(byte[], int, int)} is called where the value to write is equal to * <tt>toReplace</tt>. * <p/> * @param toReplace the byte array to replace * @param replaceWith the byte array to be substituted * @param maxReplacements the maximum number of replacements that should be made */ public void addFilter(final byte[] toReplace, final byte[] replaceWith, final int maxReplacements) { multiByteFilters.add(new Filter(toReplace, replaceWith, maxReplacements)); } /** * Causes this stream to write <tt>replaceWith</tt> in place of * <tt>toReplace</tt> if {@link #write(int)} is called where the value to write is equal to * <tt>toReplace</tt>. * <p/> * @param toReplace the byte to replace * @param replaceWith the byte to be substituted */ public void addFilter(final byte toReplace, final byte replaceWith) { addFilter(toReplace, replaceWith, -1); } /** * Causes this stream to write <tt>replaceWith</tt> in place of * <tt>toReplace</tt> if {@link #write(int)} is called where the value to write is equal to * <tt>toReplace</tt>. * <p/> * @param toReplace the byte to replace * @param replaceWith the byte to be substituted * @param maxReplacements the maximum number of replacements that should be made */ public void addFilter(final byte toReplace, final byte replaceWith, final int maxReplacements) { singleByteFilters.add(new Filter(new byte[]{toReplace}, new byte[]{replaceWith}, maxReplacements)); } static class Filter { final byte[] toReplace; final byte[] replaceWith; final int maxMatches; int numMatches = 0; public Filter(final byte[] toReplace, final byte[] replaceWith, final int maxReplacements) { this.toReplace = toReplace; this.replaceWith = replaceWith; this.maxMatches = maxReplacements; } public boolean matches(final byte candidate) { return matches(new byte[]{candidate}, 0, 1); } public boolean matches(final byte[] candidate, final int offset, final int length) { final boolean finishedReplacing = (numMatches >= maxMatches && maxMatches > -1); if (finishedReplacing || (length != toReplace.length)) { return false; } final byte[] compare; if (length == candidate.length) { compare = candidate; } else { compare = new byte[length]; System.arraycopy(candidate, offset, compare, 0, length); } final boolean match = Arrays.equals(compare, toReplace); if (match) { numMatches++; } return match; } } }