blob: 01a21e1df69917e13b4276cc999c574bbf80498f [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// 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
//
// https://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.
/**
* This is a Java Native Interface (JNI) wrapper for the Detokenizer class.
*
* <p>This classes uses the Android Base64 library instead of the standard Java Base64, which is not
* yet available on Android. To use this class outside of Android, replace android.util.Base64 with
* java.util.Base64.
*/
package dev.pigweed.tokenizer;
import android.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** This class provides the Java interface for the C++ Detokenizer class. */
public class Detokenizer {
// Android's Base64 library doesn't seem to check if the Base64-encoded data is valid. This
// regular expression checks that it is. Does not match URL-safe or unpadded Base64.
private static final Pattern TOKENIZED_STRING =
Pattern.compile("\\$([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?");
static {
System.loadLibrary("detokenizer");
}
// The handle to the C++ detokenizer instance.
private final long handle;
public Detokenizer() {
this(new byte[0]);
}
public Detokenizer(byte[] tokenDatabase) {
handle = newNativeDetokenizer(tokenDatabase);
}
/**
* Detokenizes and replaces all recognized tokenized messages ($ followed by Base64) in the
* provided string. Unrecognized tokenized strings are left unchanged.
*/
public String detokenize(String message) {
Matcher matcher = TOKENIZED_STRING.matcher(message);
StringBuilder result = new StringBuilder();
int lastIndex = 0;
while (matcher.find()) {
result.append(message, lastIndex, matcher.start());
String decoded =
detokenizeNative(handle, Base64.decode(matcher.group().substring(1), Base64.DEFAULT));
result.append(decoded != null ? decoded : matcher.group());
lastIndex = matcher.end();
}
result.append(message, lastIndex, message.length());
return result.toString();
}
/** Deletes memory allocated in C++ when this class is garbage collected. */
@Override
protected void finalize() {
deleteNativeDetokenizer(handle);
}
/** Creates a new detokenizer using the provided data as the database. */
private static native long newNativeDetokenizer(byte[] data);
/** Deletes the detokenizer object with the provided handle, which MUST be valid. */
private static native void deleteNativeDetokenizer(long handle);
/**
* Returns the detokenized version of the provided data. This is non-static so this object has a
* reference held while the function is running, which prevents finalize from running before
* detokenizeNative finishes.
*/
private native String detokenizeNative(long handle, byte[] data);
}