Remove guava dependency from the API artifact. (#1393)

* Remove guava dependency from the API artifact.

* Make LowerCaseBase16Encoding static and remove import control.
diff --git a/api/build.gradle b/api/build.gradle
index 6d527c0..e494ce0 100644
--- a/api/build.gradle
+++ b/api/build.gradle
@@ -1,8 +1,7 @@
 description = 'OpenCensus API'
 
 dependencies {
-    compile libraries.grpc_context,
-            libraries.guava
+    compile libraries.grpc_context
 
     compileOnly libraries.auto_value
 
diff --git a/api/src/main/java/io/opencensus/trace/SpanId.java b/api/src/main/java/io/opencensus/trace/SpanId.java
index 06ff045..44119d6 100644
--- a/api/src/main/java/io/opencensus/trace/SpanId.java
+++ b/api/src/main/java/io/opencensus/trace/SpanId.java
@@ -16,8 +16,8 @@
 
 package io.opencensus.trace;
 
-import com.google.common.io.BaseEncoding;
 import io.opencensus.internal.Utils;
+import io.opencensus.trace.internal.LowerCaseBase16Encoding;
 import java.util.Arrays;
 import java.util.Random;
 import javax.annotation.Nullable;
@@ -109,8 +109,7 @@
   public static SpanId fromLowerBase16(CharSequence src) {
     Utils.checkArgument(
         src.length() == HEX_SIZE, "Invalid size: expected %s, got %s", HEX_SIZE, src.length());
-    byte[] bytes = BaseEncoding.base16().lowerCase().decode(src);
-    return new SpanId(bytes);
+    return new SpanId(LowerCaseBase16Encoding.decodeToBytes(src));
   }
 
   /**
@@ -177,11 +176,7 @@
    * @since 0.11
    */
   public String toLowerBase16() {
-    return toLowerBase16(bytes);
-  }
-
-  private static String toLowerBase16(byte[] bytes) {
-    return BaseEncoding.base16().lowerCase().encode(bytes);
+    return LowerCaseBase16Encoding.encodeToString(bytes);
   }
 
   @Override
@@ -205,7 +200,7 @@
 
   @Override
   public String toString() {
-    return "SpanId{spanId=" + toLowerBase16(bytes) + "}";
+    return "SpanId{spanId=" + toLowerBase16() + "}";
   }
 
   @Override
diff --git a/api/src/main/java/io/opencensus/trace/TraceId.java b/api/src/main/java/io/opencensus/trace/TraceId.java
index c7aa48f..983b21c 100644
--- a/api/src/main/java/io/opencensus/trace/TraceId.java
+++ b/api/src/main/java/io/opencensus/trace/TraceId.java
@@ -16,9 +16,9 @@
 
 package io.opencensus.trace;
 
-import com.google.common.io.BaseEncoding;
 import io.opencensus.common.Internal;
 import io.opencensus.internal.Utils;
+import io.opencensus.trace.internal.LowerCaseBase16Encoding;
 import java.util.Arrays;
 import java.util.Random;
 import javax.annotation.Nullable;
@@ -110,8 +110,7 @@
   public static TraceId fromLowerBase16(CharSequence src) {
     Utils.checkArgument(
         src.length() == HEX_SIZE, "Invalid size: expected %s, got %s", HEX_SIZE, src.length());
-    byte[] bytes = BaseEncoding.base16().lowerCase().decode(src);
-    return new TraceId(bytes);
+    return new TraceId(LowerCaseBase16Encoding.decodeToBytes(src));
   }
 
   /**
@@ -178,11 +177,7 @@
    * @since 0.11
    */
   public String toLowerBase16() {
-    return toLowerBase16(bytes);
-  }
-
-  private static String toLowerBase16(byte[] bytes) {
-    return BaseEncoding.base16().lowerCase().encode(bytes);
+    return LowerCaseBase16Encoding.encodeToString(bytes);
   }
 
   /**
@@ -227,7 +222,7 @@
 
   @Override
   public String toString() {
-    return "TraceId{traceId=" + toLowerBase16(bytes) + "}";
+    return "TraceId{traceId=" + toLowerBase16() + "}";
   }
 
   @Override
diff --git a/api/src/main/java/io/opencensus/trace/internal/LowerCaseBase16Encoding.java b/api/src/main/java/io/opencensus/trace/internal/LowerCaseBase16Encoding.java
new file mode 100644
index 0000000..d9b2cf0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/internal/LowerCaseBase16Encoding.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018, OpenCensus 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
+ *
+ *     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 io.opencensus.trace.internal;
+
+import io.opencensus.common.Internal;
+import io.opencensus.internal.Utils;
+import java.util.Arrays;
+
+/** Internal copy of the Guava implementation of the {@code BaseEncoding.base16().lowerCase()}. */
+@Internal
+public final class LowerCaseBase16Encoding {
+  private static final String ALPHABET = "0123456789abcdef";
+  private static final int ASCII_CHARACTERS = 128;
+  private static final char[] ENCODING = buildEncodingArray();
+  private static final byte[] DECODING = buildDecodingArray();
+
+  private static char[] buildEncodingArray() {
+    char[] encoding = new char[512];
+    for (int i = 0; i < 256; ++i) {
+      encoding[i] = ALPHABET.charAt(i >>> 4);
+      encoding[i | 0x100] = ALPHABET.charAt(i & 0xF);
+    }
+    return encoding;
+  }
+
+  private static byte[] buildDecodingArray() {
+    byte[] decoding = new byte[ASCII_CHARACTERS];
+    Arrays.fill(decoding, (byte) -1);
+    for (int i = 0; i < ALPHABET.length(); i++) {
+      char c = ALPHABET.charAt(i);
+      decoding[c] = (byte) i;
+    }
+    return decoding;
+  }
+
+  /**
+   * Encodes the specified byte array, and returns the encoded {@code String}.
+   *
+   * @param bytes byte array to be encoded.
+   * @return the encoded {@code String}.
+   */
+  public static String encodeToString(byte[] bytes) {
+    StringBuilder stringBuilder = new StringBuilder(bytes.length * 2);
+    for (byte byteVal : bytes) {
+      int b = byteVal & 0xFF;
+      stringBuilder.append(ENCODING[b]);
+      stringBuilder.append(ENCODING[b | 0x100]);
+    }
+    return stringBuilder.toString();
+  }
+
+  /**
+   * Decodes the specified character sequence, and returns the resulting {@code byte[]}.
+   *
+   * @param chars the character sequence to be decoded.
+   * @return the resulting {@code byte[]}
+   * @throws IllegalArgumentException if the input is not a valid encoded string according to this
+   *     encoding.
+   */
+  public static byte[] decodeToBytes(CharSequence chars) {
+    Utils.checkArgument(chars.length() % 2 == 0, "Invalid input length " + chars.length());
+    int bytesWritten = 0;
+    byte[] bytes = new byte[chars.length() / 2];
+    for (int i = 0; i < chars.length(); i += 2) {
+      bytes[bytesWritten++] = decodeByte(chars.charAt(i), chars.charAt(i + 1));
+    }
+    return bytes;
+  }
+
+  private static byte decodeByte(char hi, char lo) {
+    Utils.checkArgument(lo < ASCII_CHARACTERS && DECODING[lo] != -1, "Invalid character " + lo);
+    Utils.checkArgument(hi < ASCII_CHARACTERS && DECODING[hi] != -1, "Invalid character " + hi);
+    int decoded = DECODING[hi] << 4 | DECODING[lo];
+    return (byte) decoded;
+  }
+
+  // Private constructor to disallow instances.
+  private LowerCaseBase16Encoding() {}
+}
diff --git a/api/src/test/java/io/opencensus/trace/internal/LowerCaseBase16EncodingTest.java b/api/src/test/java/io/opencensus/trace/internal/LowerCaseBase16EncodingTest.java
new file mode 100644
index 0000000..bcc51ab
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/internal/LowerCaseBase16EncodingTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018, OpenCensus 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
+ *
+ *     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 io.opencensus.trace.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.nio.charset.Charset;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link LowerCaseBase16Encoding}. */
+@RunWith(JUnit4.class)
+public class LowerCaseBase16EncodingTest {
+  private static final Charset CHARSET = Charset.forName("UTF-8");
+
+  @Rule public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void valid_EncodeDecode() {
+    testEncoding("", "");
+    testEncoding("f", "66");
+    testEncoding("fo", "666f");
+    testEncoding("foo", "666f6f");
+    testEncoding("foob", "666f6f62");
+    testEncoding("fooba", "666f6f6261");
+    testEncoding("foobar", "666f6f626172");
+  }
+
+  @Test
+  public void invalidDecodings_UnrecongnizedCharacters() {
+    // These contain bytes not in the decoding.
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid character g");
+    LowerCaseBase16Encoding.decodeToBytes("efhg");
+  }
+
+  @Test
+  public void invalidDecodings_InvalidInputLength() {
+    // Valid base16 strings always have an even length.
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid input length 3");
+    LowerCaseBase16Encoding.decodeToBytes("abc");
+  }
+
+  @Test
+  public void invalidDecodings_InvalidInputLengthAndCharacter() {
+    // These have a combination of invalid length and unrecognized characters.
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid input length 1");
+    LowerCaseBase16Encoding.decodeToBytes("?");
+  }
+
+  private static void testEncoding(String decoded, String encoded) {
+    testEncodes(decoded, encoded);
+    testDecodes(encoded, decoded);
+  }
+
+  private static void testEncodes(String decoded, String encoded) {
+    assertThat(LowerCaseBase16Encoding.encodeToString(decoded.getBytes(CHARSET)))
+        .isEqualTo(encoded);
+  }
+
+  private static void testDecodes(String encoded, String decoded) {
+    assertThat(LowerCaseBase16Encoding.decodeToBytes(encoded)).isEqualTo(decoded.getBytes(CHARSET));
+  }
+}
diff --git a/buildscripts/import-control.xml b/buildscripts/import-control.xml
index 7375eb5..d0000c5 100644
--- a/buildscripts/import-control.xml
+++ b/buildscripts/import-control.xml
@@ -54,9 +54,6 @@
     <allow pkg="io.opencensus.internal"/>
     <allow pkg="io.opencensus.trace"/>
 
-    <!-- TODO(#1081): Remove this dependency on Guava. -->
-    <allow class="com.google.common.io.BaseEncoding"/>
-
     <!-- These dependencies on impl/implcore are only needed by -->
     <!-- io.opencensus.trace.TraceComponentImpl and io.opencensus.trace.TraceComponentImplLite, -->
     <!-- which are deprecated. -->
diff --git a/contrib/grpc_metrics/build.gradle b/contrib/grpc_metrics/build.gradle
index db69819..a2de78d 100644
--- a/contrib/grpc_metrics/build.gradle
+++ b/contrib/grpc_metrics/build.gradle
@@ -8,7 +8,8 @@
 }
 
 dependencies {
-    compile project(':opencensus-api')
+    compile project(':opencensus-api'),
+            libraries.guava
 
     signature "org.codehaus.mojo.signature:java17:1.0@signature"
     signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
diff --git a/exporters/stats/prometheus/build.gradle b/exporters/stats/prometheus/build.gradle
index 0fa0dec..fe8563c 100644
--- a/exporters/stats/prometheus/build.gradle
+++ b/exporters/stats/prometheus/build.gradle
@@ -9,6 +9,7 @@
     compileOnly libraries.auto_value
 
     compile project(':opencensus-api'),
+            libraries.guava,
             libraries.prometheus_simpleclient
 
     testCompile project(':opencensus-api')
diff --git a/exporters/stats/signalfx/build.gradle b/exporters/stats/signalfx/build.gradle
index 9dcb1ef..d496b1e 100644
--- a/exporters/stats/signalfx/build.gradle
+++ b/exporters/stats/signalfx/build.gradle
@@ -8,7 +8,8 @@
 dependencies {
     compileOnly libraries.auto_value
 
-    compile project(':opencensus-api')
+    compile project(':opencensus-api'),
+            libraries.guava
 
     compile (libraries.signalfx_java) {
         // Prefer library version.
diff --git a/exporters/stats/stackdriver/build.gradle b/exporters/stats/stackdriver/build.gradle
index 72a0381..0bc302a 100644
--- a/exporters/stats/stackdriver/build.gradle
+++ b/exporters/stats/stackdriver/build.gradle
@@ -10,7 +10,8 @@
 
     compile project(':opencensus-api'),
             project(':opencensus-contrib-monitored-resource-util'),
-            libraries.google_auth
+            libraries.google_auth,
+            libraries.guava
 
     compile (libraries.google_cloud_monitoring) {
         // Prefer library version.
diff --git a/exporters/trace/instana/build.gradle b/exporters/trace/instana/build.gradle
index ca30e2d..028bc20 100644
--- a/exporters/trace/instana/build.gradle
+++ b/exporters/trace/instana/build.gradle
@@ -6,7 +6,8 @@
 }
 
 dependencies {
-    compile project(':opencensus-api')
+    compile project(':opencensus-api'),
+            libraries.guava
 
     testCompile project(':opencensus-api')
 
diff --git a/exporters/trace/jaeger/build.gradle b/exporters/trace/jaeger/build.gradle
index 252950f..04829aa 100644
--- a/exporters/trace/jaeger/build.gradle
+++ b/exporters/trace/jaeger/build.gradle
@@ -17,7 +17,8 @@
 }
 
 dependencies {
-    compile project(':opencensus-api')
+    compile project(':opencensus-api'),
+            libraries.guava
 
     compile(libraries.jaeger_reporter) {
         // Prefer library version.
diff --git a/exporters/trace/stackdriver/build.gradle b/exporters/trace/stackdriver/build.gradle
index 8af1d96..83dc970 100644
--- a/exporters/trace/stackdriver/build.gradle
+++ b/exporters/trace/stackdriver/build.gradle
@@ -10,7 +10,8 @@
 
     compile project(':opencensus-api'),
             project(':opencensus-contrib-monitored-resource-util'),
-            libraries.google_auth
+            libraries.google_auth,
+            libraries.guava
 
     compile (libraries.google_cloud_trace) {
         // Prefer library version.
diff --git a/exporters/trace/zipkin/build.gradle b/exporters/trace/zipkin/build.gradle
index f36e561..530dff7 100644
--- a/exporters/trace/zipkin/build.gradle
+++ b/exporters/trace/zipkin/build.gradle
@@ -7,6 +7,7 @@
 
 dependencies {
     compile project(':opencensus-api'),
+            libraries.guava,
             libraries.zipkin_reporter,
             libraries.zipkin_urlconnection
 
diff --git a/testing/build.gradle b/testing/build.gradle
index 33008e2..811b059 100644
--- a/testing/build.gradle
+++ b/testing/build.gradle
@@ -1,7 +1,8 @@
 description = 'OpenCensus Testing'
 
 dependencies {
-    compile project(':opencensus-api')
+    compile project(':opencensus-api'),
+            libraries.guava
 
     testCompile project(':opencensus-api')