aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/junitrunner
diff options
context:
space:
mode:
authorGravatar Irina Iancu <elenairina@google.com>2016-09-02 12:57:42 +0000
committerGravatar Kristina Chodorow <kchodorow@google.com>2016-09-06 15:37:24 +0000
commitd9d524ab77ebff673009e7405678c237bdb510fc (patch)
tree8fd60f11e9aa8a37ed55f00447b0b4fb818373a5 /src/java_tools/junitrunner
parent7ef4273d14398575303f651e48ec828c21218b3f (diff)
Removing GUAVA XML escapers from junitrunner.
Bazel users that are using a different Guava version than the one in the junitrunner jar are getting an IncompatibleClassChangeError. Rewriting parts of junitrunner code so it won't depend on Guava anymore. Continuing progress on issue #1150. -- MOS_MIGRATED_REVID=132054901
Diffstat (limited to 'src/java_tools/junitrunner')
-rw-r--r--src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/XmlWriter.java2
-rw-r--r--src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/CharEscaper.java184
-rw-r--r--src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/XmlEscapers.java137
3 files changed, 322 insertions, 1 deletions
diff --git a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/XmlWriter.java b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/XmlWriter.java
index 0910486d12..d60fea3e33 100644
--- a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/XmlWriter.java
+++ b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/model/XmlWriter.java
@@ -16,7 +16,7 @@ package com.google.testing.junit.runner.model;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.common.xml.XmlEscapers;
+import com.google.testing.junit.runner.util.XmlEscapers;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
diff --git a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/CharEscaper.java b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/CharEscaper.java
new file mode 100644
index 0000000000..f7b1d86f5a
--- /dev/null
+++ b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/CharEscaper.java
@@ -0,0 +1,184 @@
+// Copyright 2016 The Bazel Authors. All Rights Reserved.
+//
+// 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.google.testing.junit.runner.util;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * An escaper that uses an array to quickly look up replacement characters for a given {@code char}
+ * value. An additional safe range is provided that determines whether {@code char} values without
+ * specific replacements are to be considered safe and left unescaped or should be escaped in a
+ * general way.
+ */
+public abstract class CharEscaper {
+ // The replacement array.
+ private final char[][] replacements;
+ // The number of elements in the replacement array.
+ private final int replacementsLength;
+ // The first character in the safe range.
+ private final char safeMin;
+ // The last character in the safe range.
+ private final char safeMax;
+
+ // The multiplier for padding to use when growing the escape buffer.
+ private static final int DEST_PAD_MULTIPLIER = 2;
+
+ public CharEscaper(Map<Character, String> replacementMap, char safeMin, char safeMax) {
+ this.replacements = createReplacementArray(replacementMap);
+ this.replacementsLength = replacements.length;
+ this.safeMin = safeMin;
+ this.safeMax = safeMax;
+ }
+
+ public final String escape(String s) {
+ if (s == null) {
+ throw new NullPointerException();
+ }
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if ((c < replacementsLength && replacements[c] != null) || c > safeMax || c < safeMin) {
+ return escapeSlow(s, i);
+ }
+ }
+ return s;
+ }
+
+ /**
+ * A thread-local destination buffer to keep us from creating new buffers. The starting size is
+ * 1024 characters.
+ */
+ private static final ThreadLocal<char[]> DEST_TL =
+ new ThreadLocal<char[]>() {
+ @Override
+ protected char[] initialValue() {
+ return new char[1024];
+ }
+ };
+
+ /**
+ * Returns the escaped form of a given literal string, starting at the given index. This method is
+ * called by the {@link #escape(String)} method when it discovers that escaping is required.
+ *
+ * @param s the literal string to be escaped
+ * @param index the index to start escaping from
+ * @return the escaped form of {@code string}
+ * @throws NullPointerException if {@code string} is null
+ */
+ final String escapeSlow(String s, int index) {
+ int slen = s.length();
+
+ // Get a destination buffer and setup some loop variables.
+ char[] dest = DEST_TL.get();
+ int destSize = dest.length;
+ int destIndex = 0;
+ int lastEscape = 0;
+
+ // Loop through the rest of the string, replacing when needed into the
+ // destination buffer, which gets grown as needed as well.
+ for (; index < slen; index++) {
+
+ // Get a replacement for the current character.
+ char[] r = escape(s.charAt(index));
+
+ // If no replacement is needed, just continue.
+ if (r == null) {
+ continue;
+ }
+
+ int rlen = r.length;
+ int charsSkipped = index - lastEscape;
+
+ // This is the size needed to add the replacement, not the full size
+ // needed by the string. We only regrow when we absolutely must, and
+ // when we do grow, grow enough to avoid excessive growing. Grow.
+ int sizeNeeded = destIndex + charsSkipped + rlen;
+ if (destSize < sizeNeeded) {
+ destSize = sizeNeeded + DEST_PAD_MULTIPLIER * (slen - index);
+ dest = growBuffer(dest, destIndex, destSize);
+ }
+
+ // If we have skipped any characters, we need to copy them now.
+ if (charsSkipped > 0) {
+ s.getChars(lastEscape, index, dest, destIndex);
+ destIndex += charsSkipped;
+ }
+
+ // Copy the replacement string into the dest buffer as needed.
+ if (rlen > 0) {
+ System.arraycopy(r, 0, dest, destIndex, rlen);
+ destIndex += rlen;
+ }
+ lastEscape = index + 1;
+ }
+
+ // Copy leftover characters if there are any.
+ int charsLeft = slen - lastEscape;
+ if (charsLeft > 0) {
+ int sizeNeeded = destIndex + charsLeft;
+ if (destSize < sizeNeeded) {
+
+ // Regrow and copy, expensive! No padding as this is the final copy.
+ dest = growBuffer(dest, destIndex, sizeNeeded);
+ }
+ s.getChars(lastEscape, slen, dest, destIndex);
+ destIndex = sizeNeeded;
+ }
+ return new String(dest, 0, destIndex);
+ }
+
+ final char[] escape(char c) {
+ if (c < replacementsLength) {
+ char[] chars = replacements[c];
+ if (chars != null) {
+ return chars;
+ }
+ }
+ if (c >= safeMin && c <= safeMax) {
+ return null;
+ }
+ return escapeUnsafe(c);
+ }
+
+ abstract char[] escapeUnsafe(char c);
+
+ /**
+ * Helper method to grow the character buffer as needed, this only happens once in a while so it's
+ * ok if it's in a method call. If the index passed in is 0 then no copying will be done.
+ */
+ private static char[] growBuffer(char[] dest, int index, int size) {
+ char[] copy = new char[size];
+ if (index > 0) {
+ System.arraycopy(dest, 0, copy, 0, index);
+ }
+ return copy;
+ }
+
+ private static char[][] createReplacementArray(Map<Character, String> map) {
+ if (map == null) {
+ throw new NullPointerException();
+ }
+ if (map.isEmpty()) {
+ return new char[0][0];
+ }
+ char max = Collections.max(map.keySet());
+ char[][] replacements = new char[max + 1][];
+ for (char c : map.keySet()) {
+ replacements[c] = map.get(c).toCharArray();
+ }
+ return replacements;
+ }
+}
+
diff --git a/src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/XmlEscapers.java b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/XmlEscapers.java
new file mode 100644
index 0000000000..7621a076bc
--- /dev/null
+++ b/src/java_tools/junitrunner/java/com/google/testing/junit/runner/util/XmlEscapers.java
@@ -0,0 +1,137 @@
+// Copyright 2016 The Bazel Authors. All Rights Reserved.
+//
+// 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.google.testing.junit.runner.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Utility class for dealing with escaping XML content and attributes.
+ */
+public class XmlEscapers {
+ private XmlEscapers() {}
+
+ private static final char MIN_ASCII_CONTROL_CHAR = 0x00;
+ private static final char MAX_ASCII_CONTROL_CHAR = 0x1F;
+
+ public static CharEscaper xmlContentEscaper() {
+ return XML_CONTENT_ESCAPER;
+ }
+
+ public static CharEscaper xmlAttributeEscaper() {
+ return XML_ATTRIBUTE_ESCAPER;
+ }
+
+ private static final CharEscaper XML_CONTENT_ESCAPER;
+ private static final CharEscaper XML_ATTRIBUTE_ESCAPER;
+
+ static {
+ Builder builder = Builder.builder();
+ builder.setSafeRange(Character.MIN_VALUE, '\uFFFD');
+ builder.setUnsafeReplacement("\uFFFD");
+
+ for (char c = MIN_ASCII_CONTROL_CHAR; c <= MAX_ASCII_CONTROL_CHAR; c++) {
+ if (c != '\t' && c != '\n' && c != '\r') {
+ builder.addEscape(c, "\uFFFD");
+ }
+ }
+
+ builder.addEscape('&', "&amp;");
+ builder.addEscape('<', "&lt;");
+ builder.addEscape('>', "&gt;");
+ XML_CONTENT_ESCAPER = builder.build();
+ builder.addEscape('\'', "&apos;");
+ builder.addEscape('"', "&quot;");
+ builder.addEscape('\t', "&#x9;");
+ builder.addEscape('\n', "&#xA;");
+ builder.addEscape('\r', "&#xD;");
+ XML_ATTRIBUTE_ESCAPER = builder.build();
+ }
+
+ /**
+ * A builder for CharEscaper.
+ */
+ static final class Builder {
+ private final Map<Character, String> replacementMap = new HashMap<>();
+ private char safeMin = Character.MIN_VALUE;
+ private char safeMax = Character.MAX_VALUE;
+ private String unsafeReplacement = null;
+
+ static Builder builder() {
+ return new Builder();
+ }
+ // The constructor is exposed via the builder() method above.
+ private Builder() {}
+
+ /**
+ * Sets the safe range of characters for the escaper. Characters in this range that have no
+ * explicit replacement are considered 'safe' and remain unescaped in the output. If
+ * {@code safeMax < safeMin} then the safe range is empty.
+ *
+ * @return the builder instance
+ */
+ Builder setSafeRange(char safeMin, char safeMax) {
+ this.safeMin = safeMin;
+ this.safeMax = safeMax;
+ return this;
+ }
+
+ /**
+ * Sets the replacement string for any characters outside the 'safe' range that have no explicit
+ * replacement. If {@code unsafeReplacement} is {@code null} then no replacement will occur, if
+ * it is {@code ""} then the unsafe characters are removed from the output.
+ *
+ * @return the builder instance
+ */
+ Builder setUnsafeReplacement(@Nullable String unsafeReplacement) {
+ this.unsafeReplacement = unsafeReplacement;
+ return this;
+ }
+
+ /**
+ * Adds a replacement string for the given input character. The specified character will be
+ * replaced by the given string whenever it occurs in the input, irrespective of whether it lies
+ * inside or outside the 'safe' range.
+ *
+ * @return the builder instance
+ * @throws NullPointerException if {@code replacement} is null
+ */
+ Builder addEscape(char c, String replacement) {
+ if (replacement == null) {
+ throw new NullPointerException();
+ }
+ // This can replace an existing character (the builder is re-usable).
+ replacementMap.put(c, replacement);
+ return this;
+ }
+
+ /**
+ * Returns a new CharEscaper based on the current state of the builder.
+ */
+ CharEscaper build() {
+ return new CharEscaper(replacementMap, safeMin, safeMax) {
+ private final char[] replacementChars =
+ unsafeReplacement != null ? unsafeReplacement.toCharArray() : null;
+
+ @Override
+ char[] escapeUnsafe(char c) {
+ return replacementChars;
+ }
+ };
+ }
+ }
+}
+