blob: 7bac079e3029a685cc1d0bc84a6a0fce90be97c4 [file] [log] [blame]
/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed 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 com.facebook.buck.cxx;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Performs an in-place find-and-replace on {@link ByteBuffer} objects, where the replacements are
* of equal length to what they're replacing.
*/
public class ByteBufferReplacer {
private final ImmutableMap<byte[], byte[]> replacements;
public ByteBufferReplacer(ImmutableMap<byte[], byte[]> replacements) {
for (Map.Entry<byte[], byte[]> entry : replacements.entrySet()) {
Preconditions.checkArgument(entry.getKey().length == entry.getValue().length);
}
this.replacements = replacements;
}
private static byte[] getBytes(String str, Charset charset) {
byte[] bytes = str.getBytes(charset);
// Strip off the byte-order-markers for UTF-16.
if (charset == Charsets.UTF_16) {
bytes = Arrays.copyOfRange(bytes, 2, bytes.length);
}
return bytes;
}
/**
* Build a replacer using the given map of paths to replacement paths, using {@code charset}
* to convert to underlying byte arrays. If the replacement paths are not long enough, use
* the given path separator to fill.
*/
public static ByteBufferReplacer fromPaths(
ImmutableMap<Path, Path> paths,
char separator,
Charset charset) {
ImmutableMap.Builder<byte[], byte[]> replacements = ImmutableMap.builder();
for (Map.Entry<Path, Path> entry : paths.entrySet()) {
String original = entry.getKey().toString();
String replacement = entry.getValue().toString();
// If the replacement string is too small, keep adding the path separator until it's long
// enough.
while (getBytes(original, charset).length > getBytes(replacement, charset).length) {
replacement += separator;
}
// Depending on what was passed in, or the character encoding, we can end up with a
// replacement string that is too long, which we can't recover from.
Preconditions.checkArgument(
getBytes(original, charset).length == getBytes(replacement, charset).length);
replacements.put(getBytes(original, charset), getBytes(replacement, charset));
}
return new ByteBufferReplacer(replacements.build());
}
/**
* Perform an in-place replacement pass over the given buffer (bounded by
* {@link java.nio.Buffer#position} and {@link java.nio.Buffer#limit}).
*
* @param buffer the buffer on which to perform replacements.
* @param maxReplacements the maximum number of replacements to perform (-1 means unlimited).
* @return the number of replacements that happened.
*/
public int replace(ByteBuffer buffer, int maxReplacements) {
CharSequence charSequence = new ByteBufferCharSequence(buffer);
int position = buffer.position();
int numReplacements = 0;
for (Map.Entry<byte[], byte[]> entry : replacements.entrySet()) {
byte[] value = entry.getValue();
// Since we can't use Pattern on a byte[], we need to convert our search byte array
// to a string. To do this we use ISO-8859-1 since it maps 1-to-1 in 0-0xFF range.
Pattern pattern = Pattern.compile(
new String(entry.getKey(), Charsets.ISO_8859_1),
Pattern.LITERAL);
Matcher matcher = pattern.matcher(charSequence);
while (matcher.find() && (numReplacements < maxReplacements || maxReplacements == -1)) {
for (int i = 0; i < value.length; i++) {
buffer.put(position + matcher.start() + i, value[i]);
}
numReplacements += 1;
}
}
return numReplacements;
}
public int replace(ByteBuffer buffer) {
return replace(buffer, -1);
}
/**
* Provides a {@link CharSequence} view of an underlying {@link ByteBuffer} using the ISO-8859-1
* character encoding.
*/
private class ByteBufferCharSequence implements CharSequence {
private final ByteBuffer buffer;
public ByteBufferCharSequence(ByteBuffer buffer) {
this.buffer = buffer;
}
@Override
public int length() {
return buffer.remaining();
}
@Override
public char charAt(int index) {
// We convert from a byte to a char here just by casting. This should be fine, since we're
// performing the search via the ISO-8859-1 charset above.
return (char) (0xFF & buffer.get(buffer.position() + index));
}
@Override
public CharSequence subSequence(int start, int end) {
ByteBuffer slice = buffer.duplicate();
slice.position(buffer.position() + start);
slice.limit(buffer.position() + end);
return new ByteBufferCharSequence(slice);
}
}
}