From 2fb43688c1ce38f5717a74d62fde5f8f86840aa2 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Thu, 2 Nov 2017 15:50:29 -0700 Subject: [PATCH 01/66] Fixing version - bumping to next available SNAPSHOT (#32) Also fixing Travis builds. --- .travis.yml | 6 ++++++ version.txt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1513d3214..98fb50e41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,11 @@ sudo: false +dist: precise language: java +addons: + # https://github.com/travis-ci/travis-ci/issues/5227#issuecomment-165131913 + hosts: + - fake-hostname-to-work-around-travis-bug + hostname: fake-hostname-to-work-around-travis-bug jdk: - oraclejdk8 - oraclejdk7 diff --git a/version.txt b/version.txt index 9084fa2f7..bc5594a19 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.0 +1.1.1-SNAPSHOT From 945647e4cb1da1525ab4ce3b4b3aed154a86d658 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Thu, 2 Nov 2017 18:32:24 -0700 Subject: [PATCH 02/66] Upgrading google-java-format plus reformatting the code (#33) --- build.gradle | 40 ++++--- .../com/google/api/core/ApiAsyncFunction.java | 6 +- .../java/com/google/api/core/BetaApi.java | 4 +- .../java/com/google/api/core/InternalApi.java | 10 +- .../google/api/pathtemplate/PathTemplate.java | 106 +++++++++++------- .../pathtemplate/TemplatedResourceName.java | 25 +++-- .../api/pathtemplate/ValidationException.java | 9 +- .../api/pathtemplate/PathTemplateTest.java | 4 +- .../TemplatedResourceNameTest.java | 1 - 9 files changed, 115 insertions(+), 90 deletions(-) diff --git a/build.gradle b/build.gradle index b30c3ffa6..ed0987288 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.2" + classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.6" } } @@ -244,19 +244,21 @@ gradle.projectsEvaluated { // Formatting tasks // ================ -task verifyLicense << { - def licenseText = new File(rootProject.rootDir, 'license-header-javadoc.txt').text - def srcFiles = [] - sourceSets - .collectMany{it.allJava.getSrcDirs()} - .each{it.eachFileRecurse(FileType.FILES, {srcFiles << new Tuple(it, it.text)})} - srcFiles = srcFiles - .findAll{it.get(0).path.endsWith(".java")} - .collect{new Tuple(it.get(0), it.get(1).replaceAll("Copyright 20[0-9]{2}", "Copyright 20xx"))} - .findAll{!it.get(1).startsWith(licenseText)} - if (srcFiles.asList().size() > 0) { - srcFiles.each({println 'missing license: ' + it.get(0)}) - throw new IllegalStateException("Above files do not have licenses") +task verifyLicense { + doLast { + def licenseText = new File(rootProject.rootDir, 'license-header-javadoc.txt').text + def srcFiles = [] + sourceSets + .collectMany{it.allJava.getSrcDirs()} + .each{it.eachFileRecurse(FileType.FILES, {srcFiles << new Tuple(it, it.text)})} + srcFiles = srcFiles + .findAll{it.get(0).path.endsWith(".java")} + .collect{new Tuple(it.get(0), it.get(1).replaceAll("Copyright 20[0-9]{2}", "Copyright 20xx"))} + .findAll{!it.get(1).startsWith(licenseText)} + if (srcFiles.asList().size() > 0) { + srcFiles.each({println 'missing license: ' + it.get(0)}) + throw new IllegalStateException("Above files do not have licenses") + } } } test.dependsOn verifyLicense @@ -280,10 +282,12 @@ test.dependsOn verifyGoogleJavaFormat // ======= task checkOutGhPages { - if (!new File('tmp_gh-pages').exists()) { - exec { - commandLine 'git', 'clone', '--branch', 'gh-pages', - '--single-branch', 'https://github.com/googleapis/api-common-java/', 'tmp_gh-pages' + doLast { + if (!new File('tmp_gh-pages').exists()) { + exec { + commandLine 'git', 'clone', '--branch', 'gh-pages', + '--single-branch', 'https://github.com/googleapis/api-common-java/', 'tmp_gh-pages' + } } } } diff --git a/src/main/java/com/google/api/core/ApiAsyncFunction.java b/src/main/java/com/google/api/core/ApiAsyncFunction.java index 8d86b163e..1de164ceb 100644 --- a/src/main/java/com/google/api/core/ApiAsyncFunction.java +++ b/src/main/java/com/google/api/core/ApiAsyncFunction.java @@ -33,14 +33,16 @@ /** * Transforms a value, possibly asynchronously. * - *

It is similar to Guava's {@code AsyncFunction}, redeclared so that Guava can be shaded. + *

+ * It is similar to Guava's {@code AsyncFunction}, redeclared so that Guava can be shaded. */ public interface ApiAsyncFunction { /** * Returns an output Future to use in place of the given input. The output Future need not be * done, making AsyncFunction suitable for asynchronous derivations. * - *

Throwing an exception from this method is equivalent to returning a failing Future. + *

+ * Throwing an exception from this method is equivalent to returning a failing Future. */ ApiFuture apply(I input) throws Exception; } diff --git a/src/main/java/com/google/api/core/BetaApi.java b/src/main/java/com/google/api/core/BetaApi.java index 1ad287f7f..8cd4c601b 100644 --- a/src/main/java/com/google/api/core/BetaApi.java +++ b/src/main/java/com/google/api/core/BetaApi.java @@ -43,8 +43,8 @@ *

* Usage guidelines: *

    - *
  1. This annotation is used only on APIs with public visibility. Internal interfaces should - * not use it.
  2. + *
  3. This annotation is used only on APIs with public visibility. Internal interfaces should not + * use it.
  4. *
  5. This annotation should only be added to new APIs. Adding it to an existing API is considered * API-breaking.
  6. *
  7. Removing this annotation from an API gives it stable status.
  8. diff --git a/src/main/java/com/google/api/core/InternalApi.java b/src/main/java/com/google/api/core/InternalApi.java index 8cbb192b0..ba24c7245 100644 --- a/src/main/java/com/google/api/core/InternalApi.java +++ b/src/main/java/com/google/api/core/InternalApi.java @@ -37,13 +37,11 @@ import java.lang.annotation.Target; /** - * Annotates a program element (class, method, package etc) which is internal to its - * containing library, not part of the public API, and should not be used by users of - * the library. + * Annotates a program element (class, method, package etc) which is internal to its containing + * library, not part of the public API, and should not be used by users of the library. * - * This annotation only makes sense on APIs that are not private. Its existence - * is necessary because Java does not have a visibility level for code within a - * compilation unit. + * This annotation only makes sense on APIs that are not private. Its existence is necessary because + * Java does not have a visibility level for code within a compilation unit. */ @BetaApi @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 7ce980491..a27f6fbcc 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -38,7 +38,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @@ -49,24 +48,27 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.annotation.Nullable; /** * Represents a path template. * - *

    Templates use the syntax of the API platform; see the protobuf of HttpRule for - * details. A template consists of a sequence of literals, wildcards, and variable bindings, - * where each binding can have a sub-path. A string representation can be parsed into an - * instance of {@link PathTemplate}, which can then be used to perform matching and instantiation. + *

    + * Templates use the syntax of the API platform; see the protobuf of HttpRule for details. A + * template consists of a sequence of literals, wildcards, and variable bindings, where each binding + * can have a sub-path. A string representation can be parsed into an instance of + * {@link PathTemplate}, which can then be used to perform matching and instantiation. * - *

    Matching and instantiation deals with unescaping and escaping using URL encoding rules. For + *

    + * Matching and instantiation deals with unescaping and escaping using URL encoding rules. For * example, if a template variable for a single segment is instantiated with a string like * {@code "a/b"}, the slash will be escaped to {@code "%2f"}. (Note that slash will not be escaped - * for a multiple-segment variable, but other characters will). The literals in the template - * itself are not escaped automatically, and must be already URL encoded. + * for a multiple-segment variable, but other characters will). The literals in the template itself + * are not escaped automatically, and must be already URL encoded. + * + *

    + * Here is an example for a template using simple variables: * - *

    Here is an example for a template using simple variables: *

      *   PathTemplate template = PathTemplate.create("v1/shelves/{shelf}/books/{book}");
      *   assert template.matches("v2/shelves") == false;
    @@ -79,6 +81,7 @@
      * 
    * * Templates can use variables which match sub-paths. Example: + * *
      *   PathTemplate template = PathTemplate.create("v1/{name=shelves/*/books/*}"};
      *   assert template.match("v1/shelves/books/b1") == null;
    @@ -87,9 +90,9 @@
      *   assert template.match("v1/shelves/s1/books/b1").equals(expectedValues);
      * 
    * - * Path templates can also be used with only wildcards. Each wildcard is associated - * with an implicit variable {@code $n}, where n is the zero-based position of the - * wildcard. Example: + * Path templates can also be used with only wildcards. Each wildcard is associated with an implicit + * variable {@code $n}, where n is the zero-based position of the wildcard. Example: + * *
      *   PathTemplate template = PathTemplate.create("shelves/*/books/*"};
      *   assert template.match("shelves/books/b1") == null;
    @@ -100,10 +103,11 @@
      *   assert values.equals(expectedValues);
      * 
    * - * Paths input to matching can use URL relative syntax to indicate a host name by prefixing the - * host name, as in {@code //somewhere.io/some/path}. The host name is matched into the special - * variable {@link #HOSTNAME_VAR}. Patterns are agnostic about host names, and the same pattern - * can be used for URL relative syntax and simple path syntax: + * Paths input to matching can use URL relative syntax to indicate a host name by prefixing the host + * name, as in {@code //somewhere.io/some/path}. The host name is matched into the special variable + * {@link #HOSTNAME_VAR}. Patterns are agnostic about host names, and the same pattern can be used + * for URL relative syntax and simple path syntax: + * *
      *   PathTemplate template = PathTemplate.create("shelves/*"};
      *   Map<String, String> expectedValues = new HashMap<>();
    @@ -115,15 +119,15 @@
      *   assert template.match("shelves/s1").equals(expectedValues);
      * 
    * - * For the representation of a resource name see {@link TemplatedResourceName}, which is based - * on path templates. + * For the representation of a resource name see {@link TemplatedResourceName}, which is + * based on path templates. */ @BetaApi public class PathTemplate { /** - * A constant identifying the special variable used for endpoint bindings in - * the result of {@link #matchFromFullName(String)}. + * A constant identifying the special variable used for endpoint bindings in the result of + * {@link #matchFromFullName(String)}. */ public static final String HOSTNAME_VAR = "$hostname"; @@ -322,16 +326,19 @@ public PathTemplate withoutVars() { } /** - * Returns a path template for the sub-path of the given variable. Example:
    +   * Returns a path template for the sub-path of the given variable. Example:
    +   *
    +   * 
        *   PathTemplate template = PathTemplate.create("v1/{name=shelves/*/books/*}");
        *   assert template.subTemplate("name").toString().equals("shelves/*/books/*");
        * 
    * * The returned template will never have named variables, but only wildcards, which are dealt with - * in matching and instantiation using '$n'-variables. See the documentation of {@link - * #match(String)} and {@link #instantiate(Map)}, respectively. + * in matching and instantiation using '$n'-variables. See the documentation of + * {@link #match(String)} and {@link #instantiate(Map)}, respectively. * - *

    For a variable which has no sub-path, this returns a path template with a single wildcard + *

    + * For a variable which has no sub-path, this returns a path template with a single wildcard * ('*'). * * @throws ValidationException if the variable does not exist in the template. @@ -413,13 +420,18 @@ public void validate(String path, String exceptionMessagePrefix) { * throws a ValidationException. The exceptionMessagePrefix parameter will be prepended to the * ValidationException message. * - *

    If the path starts with '//', the first segment will be interpreted as a host name and - * stored in the variable {@link #HOSTNAME_VAR}. + *

    + * If the path starts with '//', the first segment will be interpreted as a host name and stored + * in the variable {@link #HOSTNAME_VAR}. + * + *

    + * See the {@link PathTemplate} class documentation for examples. * - *

    See the {@link PathTemplate} class documentation for examples. + *

    + * For free wildcards in the template, the matching process creates variables named '$n', where + * 'n' is the wildcard's position in the template (starting at n=0). For example: * - *

    For free wildcards in the template, the matching process creates variables named '$n', where - * 'n' is the wildcard's position in the template (starting at n=0). For example:

    +   * 
        *   PathTemplate template = PathTemplate.create("shelves/*/books/*");
        *   Map<String, String> expectedValues = new HashMap<>();
        *   expectedValues.put("$0", "s1");
    @@ -462,13 +474,18 @@ public boolean matches(String path) {
        * will be properly unescaped using URL encoding rules. If the path does not match the template,
        * null is returned.
        *
    -   * 

    If the path starts with '//', the first segment will be interpreted as a host name and - * stored in the variable {@link #HOSTNAME_VAR}. + *

    + * If the path starts with '//', the first segment will be interpreted as a host name and stored + * in the variable {@link #HOSTNAME_VAR}. + * + *

    + * See the {@link PathTemplate} class documentation for examples. * - *

    See the {@link PathTemplate} class documentation for examples. + *

    + * For free wildcards in the template, the matching process creates variables named '$n', where + * 'n' is the wildcard's position in the template (starting at n=0). For example: * - *

    For free wildcards in the template, the matching process creates variables named '$n', where - * 'n' is the wildcard's position in the template (starting at n=0). For example:

    +   * 
        *   PathTemplate template = PathTemplate.create("shelves/*/books/*");
        *   Map<String, String> expectedValues = new HashMap<>();
        *   expectedValues.put("$0", "s1");
    @@ -491,7 +508,9 @@ public Map match(String path) {
     
       /**
        * Matches the path, where the first segment is interpreted as the host name regardless of whether
    -   * it starts with '//' or not. Example: 
    +   * it starts with '//' or not. Example:
    +   *
    +   * 
        *   Map<String, String> expectedValues = new HashMap<>();
        *   expectedValues.put(HOSTNAME_VAR, "//somewhere.io");
        *   expectedValues.put("name", "shelves/s1");
    @@ -618,9 +637,10 @@ private boolean match(
        * Instantiate the template based on the given variable assignment. Performs proper URL escaping
        * of variable assignments.
        *
    -   * 

    Note that free wildcards in the template must have bindings of '$n' variables, where 'n' is - * the position of the wildcard (starting at 0). See the documentation of {@link #match(String)} - * for details. + *

    + * Note that free wildcards in the template must have bindings of '$n' variables, where 'n' is the + * position of the wildcard (starting at 0). See the documentation of {@link #match(String)} for + * details. * * @throws ValidationException if a variable occurs in the template without a binding. */ @@ -641,7 +661,9 @@ public String instantiate(String... keysAndValues) { /** * Same like {@link #instantiate(Map)} but allows for unbound variables, which are substituted - * using their original syntax. Example:

    +   * using their original syntax. Example:
    +   *
    +   * 
        *   PathTemplate template = PathTemplate.create("v1/shelves/{shelf}/books/{book}");
        *   Map<String, String> partialMap = new HashMap<>();
        *   partialMap.put("shelf", "s1");
    @@ -957,8 +979,8 @@ public String toString() {
       }
     
       /**
    -   * Returns a raw version of the template as a string. This renders the template in its
    -   * internal, normalized form.
    +   * Returns a raw version of the template as a string. This renders the template in its internal,
    +   * normalized form.
        */
       public String toRawString() {
         return toSyntax(segments, false);
    diff --git a/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java b/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java
    index 00395d15b..db49858d8 100644
    --- a/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java
    +++ b/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java
    @@ -44,16 +44,19 @@
     /**
      * Class for representing and working with resource names.
      *
    - * 

    A resource name is represented by {@link PathTemplate}, an assignment to variables in the + *

    + * A resource name is represented by {@link PathTemplate}, an assignment to variables in the * template, and an optional endpoint. The {@code ResourceName} class implements the map interface * (unmodifiable) to work with the variable assignments, and has methods to reproduce the string * representation of the name, to construct new names, and to dereference names into resources. * - *

    As a resource name essentially represents a match of a path template against a string, it can - * be also used for other purposes than naming resources. However, not all provided methods may make + *

    + * As a resource name essentially represents a match of a path template against a string, it can be + * also used for other purposes than naming resources. However, not all provided methods may make * sense in all applications. * - *

    Usage examples: + *

    + * Usage examples: * *

      *   PathTemplate template = PathTemplate.create("shelves/*/books/*");
    @@ -91,8 +94,8 @@ public  T resolve(Class resourceType, TemplatedResourceName name, String v
           };
     
       /**
    -   * Sets the resource name resolver which is used by the {@link #resolve(Class, String)} method.
    -   * By default, no resolver is registered.
    +   * Sets the resource name resolver which is used by the {@link #resolve(Class, String)} method. By
    +   * default, no resolver is registered.
        */
       public static void registerResourceNameResolver(Resolver resolver) {
         resourceNameResolver = resolver;
    @@ -218,8 +221,8 @@ public TemplatedResourceName parentName() {
       }
     
       /**
    -   * Returns true of the resource name starts with the parent resource name, i.e. is a child
    -   * of the parent.
    +   * Returns true of the resource name starts with the parent resource name, i.e. is a child of the
    +   * parent.
        */
       public boolean startsWith(TemplatedResourceName parentName) {
         // TODO: more efficient implementation.
    @@ -227,9 +230,9 @@ public boolean startsWith(TemplatedResourceName parentName) {
       }
     
       /**
    -   * Attempts to resolve a resource name into a resource, by calling the associated API.
    -   * The resource name must have an endpoint. An optional version can be specified to
    -   * determine in which version of the API to call.
    +   * Attempts to resolve a resource name into a resource, by calling the associated API. The
    +   * resource name must have an endpoint. An optional version can be specified to determine in which
    +   * version of the API to call.
        */
       public  T resolve(Class resourceType, @Nullable String version) {
         Preconditions.checkArgument(hasEndpoint(), "Resource name must have an endpoint.");
    diff --git a/src/main/java/com/google/api/pathtemplate/ValidationException.java b/src/main/java/com/google/api/pathtemplate/ValidationException.java
    index 7f6711f6e..7ecba91ef 100644
    --- a/src/main/java/com/google/api/pathtemplate/ValidationException.java
    +++ b/src/main/java/com/google/api/pathtemplate/ValidationException.java
    @@ -32,13 +32,12 @@
     package com.google.api.pathtemplate;
     
     import com.google.api.core.BetaApi;
    -
     import java.util.Stack;
     
     /**
      * Exception thrown if there is a validation problem with a path template, http config, or related
    - * framework methods. Comes as an illegal argument exception subclass. Allows to globally
    - * set a thread-local validation context description which each exception inherits.
    + * framework methods. Comes as an illegal argument exception subclass. Allows to globally set a
    + * thread-local validation context description which each exception inherits.
      */
     @BetaApi
     public class ValidationException extends IllegalArgumentException {
    @@ -50,8 +49,8 @@ public interface Supplier {
       private static ThreadLocal>> contextLocal = new ThreadLocal<>();
     
       /**
    -   * Sets the validation context description. Each thread has its own description, so
    -   * this is thread safe.
    +   * Sets the validation context description. Each thread has its own description, so this is thread
    +   * safe.
        */
       public static void pushCurrentThreadValidationContext(Supplier supplier) {
         Stack> stack = contextLocal.get();
    diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java
    index d3bb87c9a..b5149ddb2 100644
    --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java
    +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java
    @@ -33,15 +33,13 @@
     
     import com.google.common.collect.ImmutableMap;
     import com.google.common.truth.Truth;
    -
    +import java.util.Map;
     import org.junit.Rule;
     import org.junit.Test;
     import org.junit.rules.ExpectedException;
     import org.junit.runner.RunWith;
     import org.junit.runners.JUnit4;
     
    -import java.util.Map;
    -
     /**
      * Tests for {@link PathTemplate}.
      */
    diff --git a/src/test/java/com/google/api/pathtemplate/TemplatedResourceNameTest.java b/src/test/java/com/google/api/pathtemplate/TemplatedResourceNameTest.java
    index 5f26f035a..77e5244a1 100644
    --- a/src/test/java/com/google/api/pathtemplate/TemplatedResourceNameTest.java
    +++ b/src/test/java/com/google/api/pathtemplate/TemplatedResourceNameTest.java
    @@ -32,7 +32,6 @@
     package com.google.api.pathtemplate;
     
     import com.google.common.truth.Truth;
    -
     import org.junit.Test;
     import org.junit.runner.RunWith;
     import org.junit.runners.JUnit4;
    
    From fdb810241c48e538cf29b78a73732edf52eed034 Mon Sep 17 00:00:00 2001
    From: Garrett Jones 
    Date: Fri, 3 Nov 2017 15:01:52 -0700
    Subject: [PATCH 03/66] Adding new annotation, InternalExtensionOnly (#34)
    
    Also fixing a warning.
    ---
     .../java/com/google/api/core/ApiFutures.java  |  6 +-
     .../java/com/google/api/core/BetaApi.java     |  4 +-
     .../java/com/google/api/core/InternalApi.java |  4 +
     .../api/core/InternalExtensionOnly.java       | 74 +++++++++++++++++++
     4 files changed, 84 insertions(+), 4 deletions(-)
     create mode 100644 src/main/java/com/google/api/core/InternalExtensionOnly.java
    
    diff --git a/src/main/java/com/google/api/core/ApiFutures.java b/src/main/java/com/google/api/core/ApiFutures.java
    index b3f3a97d0..c9caee980 100644
    --- a/src/main/java/com/google/api/core/ApiFutures.java
    +++ b/src/main/java/com/google/api/core/ApiFutures.java
    @@ -105,9 +105,9 @@ public static  ApiFuture> allAsList(
         return new ListenableFutureToApiFuture<>(
             Futures.allAsList(
                 Iterables.transform(
    -                (Iterable>) futures,
    -                new Function, ListenableFuture>() {
    -                  public ListenableFuture apply(ApiFuture apiFuture) {
    +                futures,
    +                new Function, ListenableFuture>() {
    +                  public ListenableFuture apply(ApiFuture apiFuture) {
                         return listenableFutureForApiFuture(apiFuture);
                       }
                     })));
    diff --git a/src/main/java/com/google/api/core/BetaApi.java b/src/main/java/com/google/api/core/BetaApi.java
    index 8cd4c601b..f35cab81b 100644
    --- a/src/main/java/com/google/api/core/BetaApi.java
    +++ b/src/main/java/com/google/api/core/BetaApi.java
    @@ -47,7 +47,9 @@
      * use it.
      * 
  9. This annotation should only be added to new APIs. Adding it to an existing API is considered * API-breaking.
  10. - *
  11. Removing this annotation from an API gives it stable status.
  12. + *
  13. Removing this annotation from an API gives it stable status, assuming the API doesn't have + * other annotations denoting instability (such as {@link InternalApi} or + * {@link InternalExtensionOnly}).
  14. *
*/ @BetaApi diff --git a/src/main/java/com/google/api/core/InternalApi.java b/src/main/java/com/google/api/core/InternalApi.java index ba24c7245..f0777f97a 100644 --- a/src/main/java/com/google/api/core/InternalApi.java +++ b/src/main/java/com/google/api/core/InternalApi.java @@ -40,8 +40,12 @@ * Annotates a program element (class, method, package etc) which is internal to its containing * library, not part of the public API, and should not be used by users of the library. * + *

* This annotation only makes sense on APIs that are not private. Its existence is necessary because * Java does not have a visibility level for code within a compilation unit. + * + *

+ * Adding this annotation to an API is considered API-breaking. */ @BetaApi @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/google/api/core/InternalExtensionOnly.java b/src/main/java/com/google/api/core/InternalExtensionOnly.java new file mode 100644 index 000000000..ad78d3bfd --- /dev/null +++ b/src/main/java/com/google/api/core/InternalExtensionOnly.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.core; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a public API that is stable for callers to use, but has no guarantee of stability for + * extension. Consequently, the API should only be extended within the package containing the API. + * + *

+ * For example: + *

+ * + *

+ * The list above is just for illustration purposes and is not exhaustive. + * + *

+ * Adding this annotation to an API is considered API-breaking. + */ +@BetaApi +@Retention(RetentionPolicy.RUNTIME) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE +}) +@Documented +public @interface InternalExtensionOnly { + /** + * Context information on why the interface/class is annotated with InternalExtensionOnly. + */ + String value() default ""; +} From c93ad3cf901048052a6890d6c706f20baa7a2172 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Tue, 7 Nov 2017 16:22:51 -0800 Subject: [PATCH 04/66] Removing @BetaApi from core classes (#35) --- src/main/java/com/google/api/core/AbstractApiFuture.java | 1 - src/main/java/com/google/api/core/AbstractApiService.java | 1 - src/main/java/com/google/api/core/ApiService.java | 1 - src/main/java/com/google/api/core/CurrentMillisClock.java | 1 - src/main/java/com/google/api/core/ForwardingApiFuture.java | 1 - src/main/java/com/google/api/core/NanoClock.java | 1 - src/main/java/com/google/api/core/SettableApiFuture.java | 1 - 7 files changed, 7 deletions(-) diff --git a/src/main/java/com/google/api/core/AbstractApiFuture.java b/src/main/java/com/google/api/core/AbstractApiFuture.java index 41fedb09a..0299c9277 100644 --- a/src/main/java/com/google/api/core/AbstractApiFuture.java +++ b/src/main/java/com/google/api/core/AbstractApiFuture.java @@ -41,7 +41,6 @@ /** * Abstract implementation of ApiFuture that mirrors {@code AbstractFuture} in Guava. */ -@BetaApi public abstract class AbstractApiFuture implements ApiFuture { private final InternalSettableFuture impl = new InternalSettableFuture(); diff --git a/src/main/java/com/google/api/core/AbstractApiService.java b/src/main/java/com/google/api/core/AbstractApiService.java index 685d6c76f..d32f1e410 100644 --- a/src/main/java/com/google/api/core/AbstractApiService.java +++ b/src/main/java/com/google/api/core/AbstractApiService.java @@ -41,7 +41,6 @@ * Base class for {@link ApiService}. Similar to Guava's {@code AbstractService} but redeclared to * that Guava can be shaded. */ -@BetaApi public abstract class AbstractApiService implements ApiService { private static final ImmutableMap guavaToGaxState = ImmutableMap.builder() diff --git a/src/main/java/com/google/api/core/ApiService.java b/src/main/java/com/google/api/core/ApiService.java index 96be83665..c843a7daf 100644 --- a/src/main/java/com/google/api/core/ApiService.java +++ b/src/main/java/com/google/api/core/ApiService.java @@ -65,7 +65,6 @@ *

* Similar to Guava's {@code Service}, but redeclared so that Guava could be shaded. */ -@BetaApi public interface ApiService { /** * Registers a {@link Listener} to be {@linkplain Executor#execute executed} on the given diff --git a/src/main/java/com/google/api/core/CurrentMillisClock.java b/src/main/java/com/google/api/core/CurrentMillisClock.java index f0ae84af8..068569ec4 100644 --- a/src/main/java/com/google/api/core/CurrentMillisClock.java +++ b/src/main/java/com/google/api/core/CurrentMillisClock.java @@ -39,7 +39,6 @@ * Implementation of the {@link ApiClock} interface, which uses {@link System#currentTimeMillis()} * as time source. */ -@BetaApi public final class CurrentMillisClock implements ApiClock, Serializable { private static final long serialVersionUID = -6019259882852183285L; diff --git a/src/main/java/com/google/api/core/ForwardingApiFuture.java b/src/main/java/com/google/api/core/ForwardingApiFuture.java index 3450c209a..95922bf14 100644 --- a/src/main/java/com/google/api/core/ForwardingApiFuture.java +++ b/src/main/java/com/google/api/core/ForwardingApiFuture.java @@ -35,7 +35,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -@BetaApi public class ForwardingApiFuture implements ApiFuture { private final ApiFuture delegate; diff --git a/src/main/java/com/google/api/core/NanoClock.java b/src/main/java/com/google/api/core/NanoClock.java index b0aab3296..8b2c04f6d 100644 --- a/src/main/java/com/google/api/core/NanoClock.java +++ b/src/main/java/com/google/api/core/NanoClock.java @@ -35,7 +35,6 @@ import java.util.concurrent.TimeUnit; /** Default implementation of the ApiClock interface, using call to System.nanoTime(). */ -@BetaApi public final class NanoClock implements ApiClock, Serializable { private static final ApiClock DEFAULT_CLOCK = new NanoClock(); diff --git a/src/main/java/com/google/api/core/SettableApiFuture.java b/src/main/java/com/google/api/core/SettableApiFuture.java index a4b85f343..eab25a744 100644 --- a/src/main/java/com/google/api/core/SettableApiFuture.java +++ b/src/main/java/com/google/api/core/SettableApiFuture.java @@ -34,7 +34,6 @@ * An {@link ApiFuture} whose result can be set. Similar to Guava's {@code SettableFuture}, but * redeclared so that Guava could be shaded. */ -@BetaApi public final class SettableApiFuture extends AbstractApiFuture { private SettableApiFuture() {} From 8d8e48a24fe83c79ce240ecfec12bcd1bf43d308 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Tue, 7 Nov 2017 16:43:23 -0800 Subject: [PATCH 05/66] Bump to 1.2.0 for release (#36) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index bc5594a19..26aaba0e8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.1-SNAPSHOT +1.2.0 From 4a19f3fa787d27c04e244522da201c0fcfc7a882 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Wed, 8 Nov 2017 14:25:09 -0800 Subject: [PATCH 06/66] Bumping to next snapshot version (#37) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 26aaba0e8..e92054796 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.0 +1.2.1-SNAPSHOT From 491811f45ab462fc945859a436744b183d830cfb Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 17 Jan 2018 08:33:08 -0800 Subject: [PATCH 07/66] Added transform and transformAsync overrides that accept an Executor (#38) --- .../java/com/google/api/core/ApiFutures.java | 27 ++++++++++ .../com/google/api/core/ApiFuturesTest.java | 50 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/main/java/com/google/api/core/ApiFutures.java b/src/main/java/com/google/api/core/ApiFutures.java index c9caee980..115cdf7a9 100644 --- a/src/main/java/com/google/api/core/ApiFutures.java +++ b/src/main/java/com/google/api/core/ApiFutures.java @@ -100,6 +100,17 @@ public static ApiFuture transform( listenableFutureForApiFuture(input), new GaxFunctionToGuavaFunction(function))); } + public static ApiFuture transform( + ApiFuture input, + final ApiFunction function, + Executor executor) { + return new ListenableFutureToApiFuture<>( + Futures.transform( + listenableFutureForApiFuture(input), + new GaxFunctionToGuavaFunction(function), + executor)); + } + public static ApiFuture> allAsList( Iterable> futures) { return new ListenableFutureToApiFuture<>( @@ -128,6 +139,22 @@ public ListenableFuture apply(I input) throws Exception { return new ListenableFutureToApiFuture<>(listenableOutput); } + public static ApiFuture transformAsync( + ApiFuture input, final ApiAsyncFunction function, Executor executor) { + ListenableFuture listenableInput = listenableFutureForApiFuture(input); + ListenableFuture listenableOutput = + Futures.transformAsync( + listenableInput, + new AsyncFunction() { + @Override + public ListenableFuture apply(I input) throws Exception { + return listenableFutureForApiFuture(function.apply(input)); + } + }, + executor); + return new ListenableFutureToApiFuture<>(listenableOutput); + } + private static ListenableFuture listenableFutureForApiFuture(ApiFuture apiFuture) { ListenableFuture listenableFuture; if (apiFuture instanceof AbstractApiFuture) { diff --git a/src/test/java/com/google/api/core/ApiFuturesTest.java b/src/test/java/com/google/api/core/ApiFuturesTest.java index ab134f6f3..c97ff694a 100644 --- a/src/test/java/com/google/api/core/ApiFuturesTest.java +++ b/src/test/java/com/google/api/core/ApiFuturesTest.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; @@ -95,6 +96,31 @@ public String apply(Integer input) { assertThat(transformedFuture.get()).isEqualTo("6"); } + @Test + public void testTransformWithExecutor() throws Exception { + SettableApiFuture inputFuture = SettableApiFuture.create(); + final AtomicInteger counter = new AtomicInteger(0); + ApiFuture transformedFuture = + ApiFutures.transform( + inputFuture, + new ApiFunction() { + @Override + public String apply(Integer input) { + return input.toString(); + } + }, + new Executor() { + @Override + public void execute(Runnable command) { + counter.incrementAndGet(); + command.run(); + } + }); + inputFuture.set(6); + assertThat(transformedFuture.get()).isEqualTo("6"); + assertThat(counter.get()).isEqualTo(1); + } + @Test public void testAllAsList() throws Exception { SettableApiFuture inputFuture1 = SettableApiFuture.create(); @@ -121,6 +147,30 @@ public ApiFuture apply(Integer input) { assertThat(outputFuture.get()).isEqualTo(1); } + @Test + public void testTransformAsyncWithExecutor() throws Exception { + ApiFuture inputFuture = ApiFutures.immediateFuture(0); + final AtomicInteger counter = new AtomicInteger(0); + ApiFuture outputFuture = + ApiFutures.transformAsync( + inputFuture, + new ApiAsyncFunction() { + @Override + public ApiFuture apply(Integer input) { + return ApiFutures.immediateFuture(input + 1); + } + }, + new Executor() { + @Override + public void execute(Runnable command) { + counter.incrementAndGet(); + command.run(); + } + }); + assertThat(outputFuture.get()).isEqualTo(1); + assertThat(counter.get()).isEqualTo(1); + } + @Test public void testImmediateFailedFuture() throws InterruptedException { ApiFuture future = From 5c9d9a9ba5a9b0866b315564e7ccca66177aaad9 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Tue, 6 Feb 2018 12:02:40 -0800 Subject: [PATCH 08/66] Deprecating *Type and getType() for resource names (#40) --- .../com/google/api/resourcenames/ResourceName.java | 7 ++++++- .../google/api/resourcenames/ResourceNameType.java | 3 +++ .../api/resourcenames/UntypedResourceName.java | 13 +++++++++++++ .../api/resourcenames/UntypedResourceNameType.java | 7 ++++++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/api/resourcenames/ResourceName.java b/src/main/java/com/google/api/resourcenames/ResourceName.java index c0d06969a..54225babc 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceName.java +++ b/src/main/java/com/google/api/resourcenames/ResourceName.java @@ -37,6 +37,11 @@ @BetaApi public interface ResourceName { - /** The ResourceNameType of the resource name object. */ + /** + * The ResourceNameType of the resource name object. + * + * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. + */ + @Deprecated public ResourceNameType getType(); } diff --git a/src/main/java/com/google/api/resourcenames/ResourceNameType.java b/src/main/java/com/google/api/resourcenames/ResourceNameType.java index 4022845d3..0fb3fabef 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceNameType.java +++ b/src/main/java/com/google/api/resourcenames/ResourceNameType.java @@ -36,6 +36,9 @@ /** * An interface that resource name types must implement. A ResourceNameType class is required for * any class that implements {@link ResourceName}. + * + * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. */ @BetaApi +@Deprecated public interface ResourceNameType {} diff --git a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java index c05677d34..e47a98975 100644 --- a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java +++ b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java @@ -48,10 +48,18 @@ private UntypedResourceName(String rawValue) { this.rawValue = Preconditions.checkNotNull(rawValue); } + /** + * @deprecated use {@link #of(ResourceName)} instead. + */ + @Deprecated public static UntypedResourceName from(ResourceName resourceName) { return new UntypedResourceName(resourceName.toString()); } + public static UntypedResourceName of(ResourceName resourceName) { + return new UntypedResourceName(resourceName.toString()); + } + public static UntypedResourceName parse(String formattedString) { return new UntypedResourceName(formattedString); } @@ -60,7 +68,12 @@ public static boolean isParsableFrom(String formattedString) { return true; } + /* + * @deprecated With Oneof types being converted to use inheritance, this is + * no longer necessary. + */ @Override + @Deprecated public ResourceNameType getType() { return UntypedResourceNameType.instance(); } diff --git a/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java b/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java index f7b79791c..873241f28 100644 --- a/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java +++ b/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java @@ -33,8 +33,13 @@ import com.google.api.core.BetaApi; -/** The ResourceNameType for {@link UntypedResourceName}. */ +/** + * The ResourceNameType for {@link UntypedResourceName}. + * + * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. + */ @BetaApi +@Deprecated public class UntypedResourceNameType implements ResourceNameType { private static UntypedResourceNameType instance = new UntypedResourceNameType(); From d8e6c278ebf6ec7e8743e69a6a015647670f031d Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Wed, 7 Feb 2018 08:47:19 -0800 Subject: [PATCH 09/66] Adding stageRelease/finalizeRelease + RELEASING.md (#41) --- RELEASING.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ build.gradle | 43 ++++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000..9f8e308bb --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,79 @@ +One-time setup +============== + +Set up Sonatype Account +----------------------- +* Sign up for a Sonatype JIRA account [here](https://issues.sonatype.org) +* Click *Sign Up* in the login box, follow instructions + +Get access to repository +------------------------ +* Go to [community support](https://issues.sonatype.org/browse/OSSRH) +* Ask for publish rights by creating an issue similar to [this one](https://issues.sonatype.org/browse/OSSRH-32031) + * You must be logged in to create a new issue + * Use the *Create* button at the top tab + +* Generate the key `gpg --gen-key` + * Keep the defaults, but specify a passphrase + +* Determine your `gpg` version: `gpg -- version` + +* Find the ID of your public key + * If you're using GPG version 1.y.z, `gpg --list-secret-keys` + * Look for the line with format `sec 2048R/ABCDEFGH 2015-11-17` + * The `ABCDEFGH` is the ID for your public key + * If you're using GPG version 2.y.z `gpg --list-secret-keys --keyid-format LONG` + * Look for line with format `sec rsa2048/ABCDEFGHIJKLMNOP` + * The `ABCDEFGHIJKLMNOP` is the ID. It is 16-byte long, but Gradle + only support 8-byte keys. Use the *last* 8 bytes of the key when + following the rest of this document. + * `gpg --export-secret-keys $HOME/.gnupg/secring.gpg` + +* Upload your public key to a public server: `gpg --send-keys --keyserver hkp://pgp.mit.edu ` + +Add deploy credential settings +------------------------ +* Create a settings file at `$HOME/.gradle/gradle.properties` with your key information and your sonatype username/password + +``` +signing.keyId= +signing.password= +signing.secretKeyRingFile=/usr/local/google/home//.gnupg/secring.gpg + +ossrhUsername= +ossrhPassword= +``` + +To prepare a release +==================== + +Update version and deploy to Sonatype +------------------------------------- +1. Update `version.txt` to the release version you want +2. Run `./gradlew stageRelease` to: + * Regenerate `gh-pages` branch containing Javadocs + * Stage artifacts on Sonatype: to the staging repository if "-SNAPSHOT" is *not* included in the version; otherwise to the snapshot repository only +3. Submit a pull request, get it reviewed, and submit + +Publish the release +------------------- +1. Run `./gradlew finalizeRelease` + * Note: this will release **ALL** versions that have been staged to Sonatype: + if you have staged versions you do not intend to release, remove these first + from the [Nexus Repository Manager](https://oss.sonatype.org/) by logging in + (upper right) and browsing staging repositories (left panel) +2. It will take some time (~10 min to ~8 hours) for the package to transition +3. Publish a new release on Github: + * Go to the [releases page](https://github.com/googleapis/api-common-java/releases) and click "Draft a new release" + in order to start a new draft. + * Make sure the "Tag Version" is `vX.Y.Z` and the "Release Title" is `X.Y.Z`, where `X.Y.Z` is the release + version as listed in the `version.txt` files. + * Add the commits since the last release into the release draft. Try to group them into sections with related + changes. Anything that is a breaking change needs to be marked with `*breaking change*`. Such changes are + only allowed for alpha/beta modules and `@BetaApi` features. + * Ensure that the format is consistent with previous releases. After adding any missing updates and + reformatting as necessary, publish the draft. + +Bump development version +------------------------ +1. Update `version.txt` to the following "-SNAPSHOT" version diff --git a/build.gradle b/build.gradle index ed0987288..61fe7b37e 100644 --- a/build.gradle +++ b/build.gradle @@ -319,7 +319,46 @@ task createApiDocsRedirect { } } -// Regenerates the gh-pages branch under tmp_gh-pages, which must be committed separately -task updateDocsWithCurrentVersion { +task publishDocs { + dependsOn 'closeAndReleaseRepository' + doLast { + exec { + workingDir './tmp_gh-pages' + commandLine 'git', 'add', '.' + } + exec { + workingDir './tmp_gh-pages' + commandLine 'git', 'commit', '-m', 'Release docs for ' + project.version + } + exec { + workingDir './tmp_gh-pages' + commandLine 'git', 'push' + } + } +} + +// 1. Regenerates the gh-pages branch under tmp_gh-pages +// 2. Stages the artifact on Sonatype +task stageRelease { dependsOn 'createApiDocsRedirect' + doLast { + exec { + // We need to spawn a new gradle build process in order to upload appropriately + // More details: http://stackoverflow.com/questions/31614735/gradle-uploadarchives-artificats-namespace-when-depending-on-a-plugin + commandLine './gradlew', 'uploadArchives' + } + } +} + +// 1. Closes and releases the artifact on Sonatype +// 2. Commits and pushes the new docs +// 3. Removes tmp_gh-pages +// Note: This task assumes that the 'stageRelease' task has been completed. +task finalizeRelease { + dependsOn 'publishDocs' + doLast { + exec { + commandLine 'rm', '-r', 'tmp_gh-pages' + } + } } From 46da59a1b996a6f14258ad2f68aa961e2a502eb1 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Tue, 20 Feb 2018 21:02:40 -0800 Subject: [PATCH 10/66] Bumping to 1.3.0 for release (#42) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index e92054796..f0bb29e76 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.1-SNAPSHOT +1.3.0 From b59ece3438d8fec543e6af4b21bef20b692e747e Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Tue, 20 Feb 2018 22:21:37 -0800 Subject: [PATCH 11/66] Fixing finalizeRelease task (#43) --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 61fe7b37e..ec8c0fa65 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,8 @@ buildscript { jcenter() } dependencies { - classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.6" + classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.6", + "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.8.0" } } @@ -21,6 +22,7 @@ apply plugin: 'idea' apply plugin: 'jacoco' apply plugin: 'signing' apply plugin: 'com.github.sherter.google-java-format' +apply plugin: 'io.codearte.nexus-staging' group = "com.google.api" archivesBaseName = "api-common" From 1a563a36484d6cdece3c6a7a0eb39c2d2a113320 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Wed, 21 Feb 2018 09:53:36 -0800 Subject: [PATCH 12/66] Bumping to snapshot version for development (#44) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f0bb29e76..5c73cd1b2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.3.0 +1.3.1-SNAPSHOT From 09e7053ec9bb32ac3d98421f78968669867f4e85 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Wed, 28 Feb 2018 15:46:59 -0800 Subject: [PATCH 13/66] Removing deprecated stuff (that won't break api-client-staging) (#45) --- .../api/resourcenames/ResourceName.java | 11 +--- .../api/resourcenames/ResourceNameType.java | 4 +- .../resourcenames/UntypedResourceName.java | 18 ------- .../UntypedResourceNameType.java | 52 ------------------- 4 files changed, 3 insertions(+), 82 deletions(-) delete mode 100644 src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java diff --git a/src/main/java/com/google/api/resourcenames/ResourceName.java b/src/main/java/com/google/api/resourcenames/ResourceName.java index 54225babc..e67227fcf 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceName.java +++ b/src/main/java/com/google/api/resourcenames/ResourceName.java @@ -35,13 +35,4 @@ /** An interface that generated resource name types must implement. */ @BetaApi -public interface ResourceName { - - /** - * The ResourceNameType of the resource name object. - * - * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. - */ - @Deprecated - public ResourceNameType getType(); -} +public interface ResourceName {} diff --git a/src/main/java/com/google/api/resourcenames/ResourceNameType.java b/src/main/java/com/google/api/resourcenames/ResourceNameType.java index 0fb3fabef..2cfe42ca4 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceNameType.java +++ b/src/main/java/com/google/api/resourcenames/ResourceNameType.java @@ -34,8 +34,8 @@ import com.google.api.core.BetaApi; /** - * An interface that resource name types must implement. A ResourceNameType class is required for - * any class that implements {@link ResourceName}. + * An interface that resource name types used to have to implement. A ResourceNameType class was + * required for any class that implements {@link ResourceName}. * * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. */ diff --git a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java index e47a98975..7e7cfe75d 100644 --- a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java +++ b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java @@ -48,14 +48,6 @@ private UntypedResourceName(String rawValue) { this.rawValue = Preconditions.checkNotNull(rawValue); } - /** - * @deprecated use {@link #of(ResourceName)} instead. - */ - @Deprecated - public static UntypedResourceName from(ResourceName resourceName) { - return new UntypedResourceName(resourceName.toString()); - } - public static UntypedResourceName of(ResourceName resourceName) { return new UntypedResourceName(resourceName.toString()); } @@ -68,16 +60,6 @@ public static boolean isParsableFrom(String formattedString) { return true; } - /* - * @deprecated With Oneof types being converted to use inheritance, this is - * no longer necessary. - */ - @Override - @Deprecated - public ResourceNameType getType() { - return UntypedResourceNameType.instance(); - } - @Override public String toString() { return rawValue; diff --git a/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java b/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java deleted file mode 100644 index 873241f28..000000000 --- a/src/main/java/com/google/api/resourcenames/UntypedResourceNameType.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2016, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.resourcenames; - -import com.google.api.core.BetaApi; - -/** - * The ResourceNameType for {@link UntypedResourceName}. - * - * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. - */ -@BetaApi -@Deprecated -public class UntypedResourceNameType implements ResourceNameType { - - private static UntypedResourceNameType instance = new UntypedResourceNameType(); - - public static UntypedResourceNameType instance() { - return instance; - } - - private UntypedResourceNameType() {} -} From 29527393f9a2b12df52378ae8058a4322146da64 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Wed, 28 Feb 2018 17:09:34 -0800 Subject: [PATCH 14/66] Bumping to 1.4.0 for release (#47) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 5c73cd1b2..88c5fb891 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.3.1-SNAPSHOT +1.4.0 From 24b3e86e275b862dcb70a6c7b93f5e488d6f03d9 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Thu, 1 Mar 2018 10:36:53 -0800 Subject: [PATCH 15/66] Bumping to snapshot version for development (#49) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 88c5fb891..d65937f10 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.4.0 +1.4.1-SNAPSHOT From a647ed5d57b2d05054cd4af1a6fd127b335041d4 Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Tue, 6 Mar 2018 14:55:58 -0800 Subject: [PATCH 16/66] Add methods to ResourceName interface (#46) --- .../api/resourcenames/ResourceName.java | 15 ++- .../resourcenames/ResourceNameFactory.java | 40 ++++++++ .../resourcenames/UntypedResourceName.java | 25 ++++- .../UntypedResourceNameTest.java | 95 +++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/google/api/resourcenames/ResourceNameFactory.java create mode 100644 src/test/java/com/google/api/resourcenames/UntypedResourceNameTest.java diff --git a/src/main/java/com/google/api/resourcenames/ResourceName.java b/src/main/java/com/google/api/resourcenames/ResourceName.java index e67227fcf..751f1fa77 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceName.java +++ b/src/main/java/com/google/api/resourcenames/ResourceName.java @@ -32,7 +32,20 @@ package com.google.api.resourcenames; import com.google.api.core.BetaApi; +import java.util.Map; /** An interface that generated resource name types must implement. */ @BetaApi -public interface ResourceName {} +public interface ResourceName { + + /** + * Return the map of each field name to its value. + */ + Map getFieldValuesMap(); + + /** + * Return the String value of the field with name fieldName. Returns null if the fieldName was not + * found. + */ + String getFieldValue(String fieldName); +} diff --git a/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java b/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java new file mode 100644 index 000000000..13e18d0c5 --- /dev/null +++ b/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.resourcenames; + +import com.google.api.core.BetaApi; + +@BetaApi +public interface ResourceNameFactory { + + /* Create a new ResourceName from a formatted String representing a ResourceName. */ + T parse(String formattedString); +} diff --git a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java index 7e7cfe75d..4e2da19c3 100644 --- a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java +++ b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java @@ -33,6 +33,8 @@ import com.google.api.core.BetaApi; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.util.Map; /** * A class to represent a {@link ResourceName} with an unknown format. This class in intended to @@ -44,6 +46,8 @@ public class UntypedResourceName implements ResourceName { private final String rawValue; + private volatile Map fieldValuesMap; + private UntypedResourceName(String rawValue) { this.rawValue = Preconditions.checkNotNull(rawValue); } @@ -57,7 +61,26 @@ public static UntypedResourceName parse(String formattedString) { } public static boolean isParsableFrom(String formattedString) { - return true; + return formattedString != null; + } + + /* Returns a map with an empty String "" as the sole key, which maps to the raw value of this ResourceName. */ + @Override + public Map getFieldValuesMap() { + if (fieldValuesMap == null) { + synchronized (this) { + if (fieldValuesMap == null) { + fieldValuesMap = ImmutableMap.of("", rawValue); + } + } + } + return fieldValuesMap; + } + + /* Returns the raw value of this ResourceName iff fieldName.equals(""), else returns null. */ + @Override + public String getFieldValue(String fieldName) { + return getFieldValuesMap().get(""); } @Override diff --git a/src/test/java/com/google/api/resourcenames/UntypedResourceNameTest.java b/src/test/java/com/google/api/resourcenames/UntypedResourceNameTest.java new file mode 100644 index 000000000..ab13287ea --- /dev/null +++ b/src/test/java/com/google/api/resourcenames/UntypedResourceNameTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2018, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.google.api.resourcenames; + +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link UntypedResourceNameTest}. + */ +@RunWith(JUnit4.class) +public class UntypedResourceNameTest { + private static final String NAME_STRING = "sunshine"; + private static final String EMPTY_STRING = ""; + + @Test + public void testGetFieldValues() { + assertTrue(UntypedResourceName.isParsableFrom(NAME_STRING)); + UntypedResourceName fooName = UntypedResourceName.parse(NAME_STRING); + + Map fieldValuesMap = fooName.getFieldValuesMap(); + assertTrue(fieldValuesMap.containsKey(EMPTY_STRING)); + assertEquals(NAME_STRING, fieldValuesMap.get(EMPTY_STRING)); + assertEquals(1, fieldValuesMap.size()); + assertEquals(null, fieldValuesMap.get(NAME_STRING)); + } + + @Test + public void testInsertIntoFieldValuesMap() { + UntypedResourceName fooName = UntypedResourceName.parse(NAME_STRING); + Map fieldValuesMap = fooName.getFieldValuesMap(); + + try { + fieldValuesMap.put(EMPTY_STRING, "foo"); + fail("fieldValuesMap should prevent insertion into internal map. "); + } catch (UnsupportedOperationException e) { + } + + try { + fieldValuesMap.put(null, "foo"); + fail("fieldValuesMap should prevent insertion into internal map. "); + } catch (UnsupportedOperationException e) { + } + + try { + fieldValuesMap.put(NAME_STRING, NAME_STRING); + fail("fieldValuesMap should prevent insertion into internal map. "); + } catch (UnsupportedOperationException e) { + } + } + + @Test + public void testNullName() { + assertFalse(UntypedResourceName.isParsableFrom(null)); + try { + UntypedResourceName fooName = UntypedResourceName.parse(null); + } catch (NullPointerException e) { + } + } +} From 78680a10e369198e42a998211bc427968f9e0bab Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Tue, 6 Mar 2018 15:49:02 -0800 Subject: [PATCH 17/66] bump to version 1.5.0 (#51) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index d65937f10..bc80560fa 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.4.1-SNAPSHOT +1.5.0 From 6de220c38f9734f4c9ac5bfce5f38e6e585b35d8 Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Fri, 25 May 2018 09:20:28 -0700 Subject: [PATCH 18/66] bump version to 1.5.1-SNAPSHOT (#52) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index bc80560fa..f825f7c7f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.0 +1.5.1-SNAPSHOT From 0b74c57f32cb03935592e2ffb3d797121f33fd28 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Fri, 25 May 2018 09:38:55 -0700 Subject: [PATCH 19/66] Promoting types in resourcenames and pathtemplate to stable (#54) --- .../google/api/pathtemplate/PathTemplate.java | 2 - .../pathtemplate/TemplatedResourceName.java | 1 - .../api/pathtemplate/ValidationException.java | 2 - .../api/resourcenames/ResourceName.java | 2 - .../resourcenames/ResourceNameFactory.java | 3 -- .../api/resourcenames/ResourceNameType.java | 44 ------------------- .../resourcenames/UntypedResourceName.java | 2 - 7 files changed, 56 deletions(-) delete mode 100644 src/main/java/com/google/api/resourcenames/ResourceNameType.java diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index a27f6fbcc..9ed440bcf 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -31,7 +31,6 @@ package com.google.api.pathtemplate; -import com.google.api.core.BetaApi; import com.google.auto.value.AutoValue; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; @@ -122,7 +121,6 @@ * For the representation of a resource name see {@link TemplatedResourceName}, which is * based on path templates. */ -@BetaApi public class PathTemplate { /** diff --git a/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java b/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java index db49858d8..bbbedeaeb 100644 --- a/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java +++ b/src/main/java/com/google/api/pathtemplate/TemplatedResourceName.java @@ -65,7 +65,6 @@ * assert resourceName.parentName().toString().equals("shelves/s1/books"); * */ -@BetaApi public class TemplatedResourceName implements Map { // ResourceName Resolver diff --git a/src/main/java/com/google/api/pathtemplate/ValidationException.java b/src/main/java/com/google/api/pathtemplate/ValidationException.java index 7ecba91ef..13057ca33 100644 --- a/src/main/java/com/google/api/pathtemplate/ValidationException.java +++ b/src/main/java/com/google/api/pathtemplate/ValidationException.java @@ -31,7 +31,6 @@ package com.google.api.pathtemplate; -import com.google.api.core.BetaApi; import java.util.Stack; /** @@ -39,7 +38,6 @@ * framework methods. Comes as an illegal argument exception subclass. Allows to globally set a * thread-local validation context description which each exception inherits. */ -@BetaApi public class ValidationException extends IllegalArgumentException { public interface Supplier { diff --git a/src/main/java/com/google/api/resourcenames/ResourceName.java b/src/main/java/com/google/api/resourcenames/ResourceName.java index 751f1fa77..8442f4fdd 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceName.java +++ b/src/main/java/com/google/api/resourcenames/ResourceName.java @@ -31,11 +31,9 @@ package com.google.api.resourcenames; -import com.google.api.core.BetaApi; import java.util.Map; /** An interface that generated resource name types must implement. */ -@BetaApi public interface ResourceName { /** diff --git a/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java b/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java index 13e18d0c5..13c1967af 100644 --- a/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java +++ b/src/main/java/com/google/api/resourcenames/ResourceNameFactory.java @@ -30,9 +30,6 @@ */ package com.google.api.resourcenames; -import com.google.api.core.BetaApi; - -@BetaApi public interface ResourceNameFactory { /* Create a new ResourceName from a formatted String representing a ResourceName. */ diff --git a/src/main/java/com/google/api/resourcenames/ResourceNameType.java b/src/main/java/com/google/api/resourcenames/ResourceNameType.java deleted file mode 100644 index 2cfe42ca4..000000000 --- a/src/main/java/com/google/api/resourcenames/ResourceNameType.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2016, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.resourcenames; - -import com.google.api.core.BetaApi; - -/** - * An interface that resource name types used to have to implement. A ResourceNameType class was - * required for any class that implements {@link ResourceName}. - * - * @deprecated With Oneof types being converted to use inheritance, this is no longer necessary. - */ -@BetaApi -@Deprecated -public interface ResourceNameType {} diff --git a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java index 4e2da19c3..19a6e1e9d 100644 --- a/src/main/java/com/google/api/resourcenames/UntypedResourceName.java +++ b/src/main/java/com/google/api/resourcenames/UntypedResourceName.java @@ -31,7 +31,6 @@ package com.google.api.resourcenames; -import com.google.api.core.BetaApi; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import java.util.Map; @@ -41,7 +40,6 @@ * support the case of a resource name string in an unexpected format - generated resource name * classes with known formats should be preferred where possible. */ -@BetaApi public class UntypedResourceName implements ResourceName { private final String rawValue; From ba4a02c065829f84142b6c544bcf14e3bde015f3 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Fri, 25 May 2018 14:37:57 -0700 Subject: [PATCH 20/66] Bump to version 1.6.0 (#55) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f825f7c7f..dc1e644a1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.1-SNAPSHOT +1.6.0 From aa77f759e89f8e90a6189146b16ffd7fefacb309 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Fri, 25 May 2018 14:57:53 -0700 Subject: [PATCH 21/66] Bumping to snapshot version (#56) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index dc1e644a1..66a7bbe68 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.6.0 +1.6.1-SNAPSHOT From 60288da7bcf23d8de7e0688e34e8ef1221389caf Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Mon, 2 Jul 2018 12:51:02 -0700 Subject: [PATCH 22/66] Add Code of Conduct --- CODE_OF_CONDUCT.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..46b2a08ea --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) From bea454c07b78db833b769d2e698f5a7994091c12 Mon Sep 17 00:00:00 2001 From: Garrett Jones Date: Mon, 30 Jul 2018 10:30:16 -0700 Subject: [PATCH 23/66] Upgrading gradle (#60) --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 78c638ac9..84c977200 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-3.1-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-4.8.1-all.zip From 9a3b8aaaf1349db733bf62684bd2204e03709f40 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Mon, 30 Jul 2018 13:34:47 -0400 Subject: [PATCH 24/66] upgrade some deps (#57) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index ec8c0fa65..1e2b47b1b 100644 --- a/build.gradle +++ b/build.gradle @@ -40,13 +40,13 @@ ext { // Shortcuts for libraries we are using libraries = [ guava: 'com.google.guava:guava:19.0', - jsr305: 'com.google.code.findbugs:jsr305:3.0.0', + jsr305: 'com.google.code.findbugs:jsr305:3.0.2', autovalue: 'com.google.auto.value:auto-value:1.1', // Testing - junit: 'junit:junit:4.11', + junit: 'junit:junit:4.12', mockito: 'org.mockito:mockito-core:1.10.19', - truth: 'com.google.truth:truth:0.27', + truth: 'com.google.truth:truth:0.42', commons: 'org.apache.commons:commons-lang3:3.4', // Formatter From 879c70dbe8f0006ddcc399ff7be3a96b20e7dd75 Mon Sep 17 00:00:00 2001 From: Hanzhen Yi <33737743+hzyi-google@users.noreply.github.com> Date: Wed, 1 Aug 2018 13:45:27 -0700 Subject: [PATCH 25/66] remove usage of deprecated methods in guava (#61) * remove usage of deprecated methods in guava * Deprecated methods parellel to those deparecated in guava --- .../java/com/google/api/core/ApiFutures.java | 56 +++++++++++++------ .../com/google/api/core/ApiFuturesTest.java | 13 +++-- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/google/api/core/ApiFutures.java b/src/main/java/com/google/api/core/ApiFutures.java index 115cdf7a9..33f82981b 100644 --- a/src/main/java/com/google/api/core/ApiFutures.java +++ b/src/main/java/com/google/api/core/ApiFutures.java @@ -46,6 +46,13 @@ public final class ApiFutures { private ApiFutures() {} + /* + * @deprecated Use {@linkplain #addCallback(ApiFuture, ApiFutureCallback, Executor) the + * overload that requires an executor}. For identical behavior, pass {@link + * com.google.common.util.concurrent.MoreExecutors#directExecutor}, but consider whether + * another executor would be safer. + */ + @Deprecated public static void addCallback( final ApiFuture future, final ApiFutureCallback callback) { addCallback(future, callback, directExecutor()); @@ -69,15 +76,31 @@ public void onSuccess(V v) { executor); } + /* + * @deprecated Use {@linkplain #catching(ApiFuture, Class, ApiFunction, Executor) the + * overload that requires an executor}. For identical behavior, pass {@link + * com.google.common.util.concurrent.MoreExecutors#directExecutor}, but consider whether + * another executor would be safer. + */ + @Deprecated public static ApiFuture catching( ApiFuture input, Class exceptionType, ApiFunction callback) { + return catching(input, exceptionType, callback, directExecutor()); + } + + public static ApiFuture catching( + ApiFuture input, + Class exceptionType, + ApiFunction callback, + Executor executor) { ListenableFuture catchingFuture = Futures.catching( listenableFutureForApiFuture(input), exceptionType, - new GaxFunctionToGuavaFunction(callback)); + new GaxFunctionToGuavaFunction(callback), + directExecutor()); return new ListenableFutureToApiFuture(catchingFuture); } @@ -93,11 +116,16 @@ public static ApiFuture immediateCancelledFuture() { return new ListenableFutureToApiFuture(Futures.immediateCancelledFuture()); } + /* + * @deprecated Use {@linkplain #transform(ApiFuture, ApiFunction, Executor) the + * overload that requires an executor}. For identical behavior, pass {@link + * com.google.common.util.concurrent.MoreExecutors#directExecutor}, but consider whether + * another executor would be safer. + */ + @Deprecated public static ApiFuture transform( ApiFuture input, final ApiFunction function) { - return new ListenableFutureToApiFuture<>( - Futures.transform( - listenableFutureForApiFuture(input), new GaxFunctionToGuavaFunction(function))); + return transform(input, function, directExecutor()); } public static ApiFuture transform( @@ -123,20 +151,16 @@ public ListenableFuture apply(ApiFuture apiFuture) { } }))); } - + /* + * @deprecated Use {@linkplain #transformAsync(ApiFuture, ApiFunction, Executor) the + * overload that requires an executor}. For identical behavior, pass {@link + * com.google.common.util.concurrent.MoreExecutors#directExecutor}, but consider whether + * another executor would be safer. + */ + @Deprecated public static ApiFuture transformAsync( ApiFuture input, final ApiAsyncFunction function) { - ListenableFuture listenableInput = listenableFutureForApiFuture(input); - ListenableFuture listenableOutput = - Futures.transformAsync( - listenableInput, - new AsyncFunction() { - @Override - public ListenableFuture apply(I input) throws Exception { - return listenableFutureForApiFuture(function.apply(input)); - } - }); - return new ListenableFutureToApiFuture<>(listenableOutput); + return transformAsync(input, function, directExecutor()); } public static ApiFuture transformAsync( diff --git a/src/test/java/com/google/api/core/ApiFuturesTest.java b/src/test/java/com/google/api/core/ApiFuturesTest.java index c97ff694a..eec0fd8f3 100644 --- a/src/test/java/com/google/api/core/ApiFuturesTest.java +++ b/src/test/java/com/google/api/core/ApiFuturesTest.java @@ -31,6 +31,7 @@ package com.google.api.core; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import com.google.common.collect.ImmutableList; import java.util.List; @@ -58,7 +59,8 @@ public void onSuccess(Integer i) { public void onFailure(Throwable t) { flag.set(-1); } - }); + }, + directExecutor()); future.set(0); assertThat(flag.get()).isEqualTo(1); } @@ -75,7 +77,8 @@ public void testCatch() throws Exception { public Integer apply(Exception ex) { return 42; } - }); + }, + directExecutor()); future.setException(new Exception()); assertThat(fallback.get()).isEqualTo(42); } @@ -91,7 +94,8 @@ public void testTransform() throws Exception { public String apply(Integer input) { return input.toString(); } - }); + }, + directExecutor()); inputFuture.set(6); assertThat(transformedFuture.get()).isEqualTo("6"); } @@ -143,7 +147,8 @@ public void testTransformAsync() throws Exception { public ApiFuture apply(Integer input) { return ApiFutures.immediateFuture(input + 1); } - }); + }, + directExecutor()); assertThat(outputFuture.get()).isEqualTo(1); } From 6812c786d7e03a6c1682bb6084c595dca6577695 Mon Sep 17 00:00:00 2001 From: Hanzhen Yi <33737743+hzyi-google@users.noreply.github.com> Date: Thu, 2 Aug 2018 11:51:28 -0700 Subject: [PATCH 26/66] bump to 1.7.0 for release (#62) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 66a7bbe68..bd8bf882d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.6.1-SNAPSHOT +1.7.0 From 032fd87bc9045670e4a7efe4a7d79f8e7fd134b0 Mon Sep 17 00:00:00 2001 From: Hanzhen Yi <33737743+hzyi-google@users.noreply.github.com> Date: Thu, 2 Aug 2018 15:32:57 -0700 Subject: [PATCH 27/66] bump to snapshot version (#63) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index bd8bf882d..6c2a6460a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.7.0 +1.7.1-SNAPSHOT From a4cc6936870c704da441d43e5c2ba78c4fb5d515 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Fri, 7 Sep 2018 14:24:36 -0400 Subject: [PATCH 28/66] Guava 20 (#67) --- build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1e2b47b1b..46d806eaa 100644 --- a/build.gradle +++ b/build.gradle @@ -39,9 +39,10 @@ ext { // Shortcuts for libraries we are using libraries = [ - guava: 'com.google.guava:guava:19.0', + guava: 'com.google.guava:guava:20.0', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', autovalue: 'com.google.auto.value:auto-value:1.1', + error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.1.3', // Testing junit: 'junit:junit:4.12', @@ -63,7 +64,8 @@ dependencies { compile libraries.guava, libraries.jsr305 - compileOnly libraries.autovalue + compileOnly libraries.autovalue, + libraries.error_prone_annotations testCompile libraries.junit, libraries.mockito, From 87d7d1763ca73872892afab60b1a3b35504b9614 Mon Sep 17 00:00:00 2001 From: ajaaym <34161822+ajaaym@users.noreply.github.com> Date: Fri, 4 Jan 2019 14:54:34 -0500 Subject: [PATCH 29/66] Fix PathTemplate matcher to match path with protocol and hostname (#68) * Fix PathTemplate matcher to match path with protocol and hostname --- .../google/api/pathtemplate/PathTemplate.java | 40 ++++++++++++++++--- .../api/pathtemplate/PathTemplateTest.java | 24 +++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 9ed440bcf..6edcddcca 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -125,13 +125,17 @@ public class PathTemplate { /** * A constant identifying the special variable used for endpoint bindings in the result of - * {@link #matchFromFullName(String)}. + * {@link #matchFromFullName(String)}. It may also contain protocol string, if its provided in the + * input. */ public static final String HOSTNAME_VAR = "$hostname"; // A regexp to match a custom verb at the end of a path. private static final Pattern CUSTOM_VERB_PATTERN = Pattern.compile(":([^/*}{=]+)$"); + // A regex to match a hostname with or without protocol. + private static final Pattern HOSTNAME_PATTERN = Pattern.compile("^(\\w+:)?//"); + // A splitter on slash. private static final Splitter SLASH_SPLITTER = Splitter.on('/').trimResults(); @@ -533,10 +537,10 @@ private Map match(String path, boolean forceHostName) { path = path.substring(0, matcher.start(0)); } - // Do full match. - boolean withHostName = path.startsWith("//"); + Matcher matcher = HOSTNAME_PATTERN.matcher(path); + boolean withHostName = matcher.find(); if (withHostName) { - path = path.substring(2); + path = matcher.replaceFirst(""); } List input = SLASH_SPLITTER.splitToList(path); int inPos = 0; @@ -548,16 +552,42 @@ private Map match(String path, boolean forceHostName) { String hostName = input.get(inPos++); if (withHostName) { // Put the // back, so we can distinguish this case from forceHostName. - hostName = "//" + hostName; + hostName = matcher.group(0) + hostName; } values.put(HOSTNAME_VAR, hostName); } + if (withHostName) { + inPos = alignInputToAlignableSegment(input, inPos, segments.get(0)); + } if (!match(input, inPos, segments, 0, values)) { return null; } return ImmutableMap.copyOf(values); } + // Aligns input to start of literal value of literal or binding segment if input contains hostname. + private int alignInputToAlignableSegment(List input, int inPos, Segment segment) { + switch (segment.kind()) { + case BINDING: + inPos = alignInputPositionToLiteral(input, inPos, segment.value() + "s"); + return inPos + 1; + case LITERAL: + return alignInputPositionToLiteral(input, inPos, segment.value()); + } + return inPos; + } + + // Aligns input to start of literal value if input contains hostname. + private int alignInputPositionToLiteral( + List input, int inPos, String literalSegmentValue) { + for (; inPos < input.size(); inPos++) { + if (literalSegmentValue.equals(input.get(inPos))) { + return inPos; + } + } + return inPos; + } + // Tries to match the input based on the segments at given positions. Returns a boolean // indicating whether the match was successful. private boolean match( diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index b5149ddb2..69d935fc1 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -83,6 +83,30 @@ public void matchWithHostName() { Truth.assertThat(match.get("$1")).isEqualTo("o"); } + @Test + public void matchWithHostNameAndProtocol() { + PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c"); + } + + @Test + public void matchWithHostNameAndProtocolWithTemplateStartWithBinding() { + PathTemplate template = PathTemplate.create("{project}/zones/{zone}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c"); + } + @Test public void matchWithCustomMethod() { PathTemplate template = PathTemplate.create("buckets/*/objects/*:custom"); From b966ff3cd2c36af259cf3e407109bf9d25731cd0 Mon Sep 17 00:00:00 2001 From: Stanislav Baiduzhyi Date: Mon, 11 Feb 2019 19:15:30 +0100 Subject: [PATCH 30/66] Automatic-Module-Name added to the manifest. (#69) * Automatic-Module-Name added to the manifest. * Update build.gradle --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 46d806eaa..363574545 100644 --- a/build.gradle +++ b/build.gradle @@ -86,6 +86,13 @@ jacocoTestReport { check.dependsOn jacocoTestReport +// jar with automatic module name: +jar { + manifest { + attributes('Automatic-Module-Name': 'com.google.api.apicommon') + } +} + // Source jar // ---------- From 196128d140f475d0bd3d92f7eb631012cfc032fc Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Tue, 19 Feb 2019 09:06:27 -0500 Subject: [PATCH 31/66] Upgrade dependencies for GCP orbit (#71) @sduskis --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 363574545..b38b97ce0 100644 --- a/build.gradle +++ b/build.gradle @@ -39,16 +39,16 @@ ext { // Shortcuts for libraries we are using libraries = [ - guava: 'com.google.guava:guava:20.0', + guava: 'com.google.guava:guava:26.0-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', autovalue: 'com.google.auto.value:auto-value:1.1', - error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.1.3', + error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.2.0', // Testing junit: 'junit:junit:4.12', mockito: 'org.mockito:mockito-core:1.10.19', truth: 'com.google.truth:truth:0.42', - commons: 'org.apache.commons:commons-lang3:3.4', + commons: 'org.apache.commons:commons-lang3:3.5', // Formatter javaFomatter: 'com.google.googlejavaformat:google-java-format:0.1-alpha' From d00d97d6985b8580e8ab682a43cb45b131a070cf Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Tue, 19 Feb 2019 09:06:43 -0500 Subject: [PATCH 32/66] Current version is 1.7.0 and is stable (#70) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 67adc2ed8..0ef1b58a3 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,10 @@ additional qualifications: public for technical reasons, because of the limitations of Java's access modifiers. For the purposes of semver, they should be considered private. -This library is currently in major version zero (``0.y.z``), which means that -anything may change at any time and the public API should not be considered -stable. +This library is currently in major version one (``1.y.z``), which means that +any public API not specifically marked as `@BetaApi` or `@InternalApi` can be considered +stable. They will not change without incrementing the major version to 2.x or later, +and probably not then. License ------- From 3fa1b08df739448017fe61206321cd98df4d0b82 Mon Sep 17 00:00:00 2001 From: Solomon Duskis Date: Fri, 8 Mar 2019 10:28:21 -0500 Subject: [PATCH 33/66] Using the executor in ApiFutures.catching (#73) An executor is passed in, but never used. --- src/main/java/com/google/api/core/ApiFutures.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/api/core/ApiFutures.java b/src/main/java/com/google/api/core/ApiFutures.java index 33f82981b..d0638f1f6 100644 --- a/src/main/java/com/google/api/core/ApiFutures.java +++ b/src/main/java/com/google/api/core/ApiFutures.java @@ -100,7 +100,7 @@ public static ApiFuture catching( listenableFutureForApiFuture(input), exceptionType, new GaxFunctionToGuavaFunction(callback), - directExecutor()); + executor); return new ListenableFutureToApiFuture(catchingFuture); } From 6e884415d6a624a00cbcc7520f5caae4c7a23262 Mon Sep 17 00:00:00 2001 From: Ryan Brewster Date: Wed, 10 Apr 2019 17:59:28 -0700 Subject: [PATCH 34/66] Modify PathTemplate so that double-star (PATH_WILDCARD) matches 0+ segments (#75) --- build.gradle | 5 +- .../google/api/pathtemplate/PathTemplate.java | 53 ++++++++++--------- .../api/pathtemplate/PathTemplateTest.java | 31 +++++++++++ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/build.gradle b/build.gradle index b38b97ce0..08dd1e559 100644 --- a/build.gradle +++ b/build.gradle @@ -36,9 +36,9 @@ targetCompatibility = 1.7 // ------------ ext { - // Shortcuts for libraries we are using libraries = [ + javax_annotations: 'javax.annotation:javax.annotation-api:1.3.1', guava: 'com.google.guava:guava:26.0-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', autovalue: 'com.google.auto.value:auto-value:1.1', @@ -62,7 +62,8 @@ repositories { dependencies { compile libraries.guava, - libraries.jsr305 + libraries.jsr305, + libraries.javax_annotations compileOnly libraries.autovalue, libraries.error_prone_annotations diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 6edcddcca..e21becd23 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -612,7 +612,8 @@ private boolean match( // This is the final segment, and this check should have already been performed by the // caller. The matching value is no longer present in the input. break; - default: + case LITERAL: + case WILDCARD: if (inPos >= input.size()) { // End of input return false; @@ -627,37 +628,41 @@ private boolean match( } if (currentVar != null) { // Create or extend current match - String current = values.get(currentVar); - if (current == null) { - values.put(currentVar, next); - } else { - values.put(currentVar, current + "/" + next); - } + values.put(currentVar, concatCaptures(values.get(currentVar), next)); } - if (seg.kind() == SegmentKind.PATH_WILDCARD) { - // Compute the number of additional input the ** can consume. This - // is possible because we restrict patterns to have only one **. - int segsToMatch = 0; - for (int i = segPos; i < segments.size(); i++) { - switch (segments.get(i).kind()) { - case BINDING: - case END_BINDING: - // skip - continue; - default: - segsToMatch++; - } - } - int available = (input.size() - inPos) - segsToMatch; - while (available-- > 0) { - values.put(currentVar, values.get(currentVar) + "/" + decodeUrl(input.get(inPos++))); + break; + case PATH_WILDCARD: + // Compute the number of additional input the ** can consume. This + // is possible because we restrict patterns to have only one **. + int segsToMatch = 0; + for (int i = segPos; i < segments.size(); i++) { + switch (segments.get(i).kind()) { + case BINDING: + case END_BINDING: + // skip + continue; + default: + segsToMatch++; } } + int available = (input.size() - inPos) - segsToMatch; + // If this segment is empty, make sure it is still captured. + if (available == 0 && !values.containsKey(currentVar)) { + values.put(currentVar, ""); + } + while (available-- > 0) { + values.put( + currentVar, concatCaptures(values.get(currentVar), decodeUrl(input.get(inPos++)))); + } } } return inPos == input.size(); } + private static String concatCaptures(@Nullable String cur, String next) { + return cur == null ? next : cur + "/" + next; + } + // Template Instantiation // ====================== diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 69d935fc1..45be25f3b 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -107,6 +107,37 @@ public void matchWithHostNameAndProtocolWithTemplateStartWithBinding() { Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c"); } + @Test + public void pathWildcards_matchZeroOrMoreSegments() { + PathTemplate start = PathTemplate.create("{glob=**}/b"); + PathTemplate middle = PathTemplate.create("a/{glob=**}/b"); + PathTemplate end = PathTemplate.create("a/{glob=**}"); + + Truth.assertThat(start.match("b").get("glob")).isEmpty(); + Truth.assertThat(start.match("/b").get("glob")).isEmpty(); + Truth.assertThat(start.match("a/b").get("glob")).isEqualTo("a"); + Truth.assertThat(start.match("a/a/a/b").get("glob")).isEqualTo("a/a/a"); + + Truth.assertThat(middle.match("a/b").get("glob")).isEmpty(); + Truth.assertThat(middle.match("a//b").get("glob")).isEmpty(); + Truth.assertThat(middle.match("a/x/b").get("glob")).isEqualTo("x"); + Truth.assertThat(middle.match("a/x/y/z/b").get("glob")).isEqualTo("x/y/z"); + + Truth.assertThat(end.match("a").get("glob")).isEmpty(); + Truth.assertThat(end.match("a/").get("glob")).isEmpty(); + Truth.assertThat(end.match("a/b").get("glob")).isEqualTo("b"); + Truth.assertThat(end.match("a/b/b/b").get("glob")).isEqualTo("b/b/b"); + } + + @Test + public void pathWildcard_canMatchTheEmptyString() { + PathTemplate template = PathTemplate.create("{glob=**}"); + + Truth.assertThat(template.match("").get("glob")).isEmpty(); + Truth.assertThat(template.match("a").get("glob")).isEqualTo("a"); + Truth.assertThat(template.match("a/b").get("glob")).isEqualTo("a/b"); + } + @Test public void matchWithCustomMethod() { PathTemplate template = PathTemplate.create("buckets/*/objects/*:custom"); From b7feaad68947d439536f4a87e7855dabe5da5f8b Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Thu, 11 Apr 2019 13:15:20 -0700 Subject: [PATCH 35/66] Release v1.8.0 (#76) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 6c2a6460a..27f9cd322 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.7.1-SNAPSHOT +1.8.0 From 16e788c51707a7bd09891067a285aac3e20b443c Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Thu, 25 Apr 2019 09:21:16 -0700 Subject: [PATCH 36/66] Bump version for development (#77) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 27f9cd322..f3609bd4d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.8.0 +1.8.1-SNAPSHOT From feb97c82f6c28ad89df941be0c6d91ad704636a0 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Thu, 25 Apr 2019 12:23:10 -0400 Subject: [PATCH 37/66] Upgrade errorprone and specify URL for project (#79) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 08dd1e559..d95645ed6 100644 --- a/build.gradle +++ b/build.gradle @@ -38,11 +38,11 @@ targetCompatibility = 1.7 ext { // Shortcuts for libraries we are using libraries = [ - javax_annotations: 'javax.annotation:javax.annotation-api:1.3.1', + javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', guava: 'com.google.guava:guava:26.0-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', autovalue: 'com.google.auto.value:auto-value:1.1', - error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.2.0', + error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.2', // Testing junit: 'junit:junit:4.12', @@ -217,7 +217,7 @@ if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword') packaging 'jar' artifactId 'api-common' description 'Common utilities for Google APIs in Java' - url 'https://github.com/googleapis' + url 'https://github.com/googleapis/api-common-java' scm { url 'https://github.com/googleapis/api-common-java' From dcb7f8143660b3afee058843f37ed6cb66f4decd Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Thu, 25 Apr 2019 09:46:42 -0700 Subject: [PATCH 38/66] Release v1.8.1 (#80) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f3609bd4d..a8fdfda1c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.8.1-SNAPSHOT +1.8.1 From 9db7d88bcc477f6c4b56db290f2991756981a7cb Mon Sep 17 00:00:00 2001 From: Andrea Lin Date: Mon, 29 Apr 2019 15:16:00 -0700 Subject: [PATCH 39/66] Bump version for development (#83) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index a8fdfda1c..4df9f77d1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.8.1 +1.8.2-SNAPSHOT From 02b953087e32a68cb7bd6c7d764d4ae2f5ad85bd Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 4 Jun 2019 14:16:03 +0200 Subject: [PATCH 40/66] Add renovate.json (#84) --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..f45d8f110 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} From 0ac1348ccc8d7fa686ce11dc655744c0275737fb Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Mon, 24 Jun 2019 11:01:48 -0400 Subject: [PATCH 41/66] workaround Gradle bug (#82) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d95645ed6..fdc5f580e 100644 --- a/build.gradle +++ b/build.gradle @@ -237,7 +237,7 @@ if (project.hasProperty('ossrhUsername') && project.hasProperty('ossrhPassword') name 'GoogleAPIs' email 'googleapis@googlegroups.com' url 'https://github.com/googleapis' - organization 'Google, Inc.' + organization = 'Google, Inc.' organizationUrl 'https://www.google.com' } } From afbd00f59c6b575e173f0f079af2e6fd295fdfac Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 24 Jun 2019 17:03:24 +0200 Subject: [PATCH 42/66] Update dependency gradle to v4.10.3 (#85) --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84c977200..f6fd8db47 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-4.8.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip From d707065c27f8643b47af4c2a2237b237ee35774a Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Thu, 29 Aug 2019 14:56:10 -0400 Subject: [PATCH 43/66] build on trusty (#81) --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98fb50e41..058f65fc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ sudo: false -dist: precise +dist: trusty language: java addons: # https://github.com/travis-ci/travis-ci/issues/5227#issuecomment-165131913 @@ -8,7 +8,6 @@ addons: hostname: fake-hostname-to-work-around-travis-bug jdk: - oraclejdk8 - - oraclejdk7 - openjdk7 script: ./gradlew check --info From 83b3a09881ea6025e2b433f4cefa21ac3da51f5c Mon Sep 17 00:00:00 2001 From: Kshithij Iyer Date: Fri, 1 Nov 2019 23:11:51 +0530 Subject: [PATCH 44/66] Adding title to CONTRIBUTING.md (#89) --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2827b7d3f..c6877c550 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,5 @@ +# How to Contribute + Want to contribute? Great! First, read this page (including the small print at the end). ### Before you contribute From bcdc81c5ea3c762b11287f950c4ada82dd37279f Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Tue, 5 Nov 2019 16:34:50 -0500 Subject: [PATCH 45/66] Update guava and Truth (#90) * Update guava and Truth * update errorprone --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index fdc5f580e..2c63217c3 100644 --- a/build.gradle +++ b/build.gradle @@ -39,15 +39,15 @@ ext { // Shortcuts for libraries we are using libraries = [ javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', - guava: 'com.google.guava:guava:26.0-android', + guava: 'com.google.guava:guava:28.1-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', autovalue: 'com.google.auto.value:auto-value:1.1', - error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.2', + error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.3', // Testing junit: 'junit:junit:4.12', mockito: 'org.mockito:mockito-core:1.10.19', - truth: 'com.google.truth:truth:0.42', + truth: 'com.google.truth:truth:1.0', commons: 'org.apache.commons:commons-lang3:3.5', // Formatter From 147f53d8475eaaf36c7c45d77b7625e366875808 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Mon, 25 Nov 2019 10:30:09 -0500 Subject: [PATCH 46/66] remove unused dependencies (#91) --- build.gradle | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 2c63217c3..0178b4dbb 100644 --- a/build.gradle +++ b/build.gradle @@ -48,10 +48,6 @@ ext { junit: 'junit:junit:4.12', mockito: 'org.mockito:mockito-core:1.10.19', truth: 'com.google.truth:truth:1.0', - commons: 'org.apache.commons:commons-lang3:3.5', - - // Formatter - javaFomatter: 'com.google.googlejavaformat:google-java-format:0.1-alpha' ] } @@ -70,8 +66,7 @@ dependencies { testCompile libraries.junit, libraries.mockito, - libraries.truth, - libraries.commons + libraries.truth } clean.doFirst { From 48e3ba559120f0bb178f87c91057353180040f5a Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Mon, 25 Nov 2019 12:07:55 -0600 Subject: [PATCH 47/66] Add Kokoro release scripts (#88) --- .kokoro/build.sh | 27 +++++++++++++++ .kokoro/common.cfg | 13 ++++++++ .kokoro/continuous/common.cfg | 24 +++++++++++++ .kokoro/continuous/java11.cfg | 7 ++++ .kokoro/continuous/java7.cfg | 7 ++++ .kokoro/continuous/java8.cfg | 7 ++++ .kokoro/presubmit/common.cfg | 24 +++++++++++++ .kokoro/presubmit/java11.cfg | 7 ++++ .kokoro/presubmit/java7.cfg | 7 ++++ .kokoro/presubmit/java8.cfg | 7 ++++ .kokoro/release/common.cfg | 49 +++++++++++++++++++++++++++ .kokoro/release/common.sh | 40 ++++++++++++++++++++++ .kokoro/release/drop.cfg | 5 +++ .kokoro/release/drop.sh | 19 +++++++++++ .kokoro/release/promote.cfg | 5 +++ .kokoro/release/promote.sh | 31 +++++++++++++++++ .kokoro/release/publish_javadoc.cfg | 19 +++++++++++ .kokoro/release/publish_javadoc.sh | 52 +++++++++++++++++++++++++++++ .kokoro/release/stage.cfg | 35 +++++++++++++++++++ .kokoro/release/stage.sh | 38 +++++++++++++++++++++ .kokoro/trampoline.sh | 24 +++++++++++++ build.gradle | 3 ++ 22 files changed, 450 insertions(+) create mode 100755 .kokoro/build.sh create mode 100644 .kokoro/common.cfg create mode 100644 .kokoro/continuous/common.cfg create mode 100644 .kokoro/continuous/java11.cfg create mode 100644 .kokoro/continuous/java7.cfg create mode 100644 .kokoro/continuous/java8.cfg create mode 100644 .kokoro/presubmit/common.cfg create mode 100644 .kokoro/presubmit/java11.cfg create mode 100644 .kokoro/presubmit/java7.cfg create mode 100644 .kokoro/presubmit/java8.cfg create mode 100644 .kokoro/release/common.cfg create mode 100755 .kokoro/release/common.sh create mode 100644 .kokoro/release/drop.cfg create mode 100755 .kokoro/release/drop.sh create mode 100644 .kokoro/release/promote.cfg create mode 100755 .kokoro/release/promote.sh create mode 100644 .kokoro/release/publish_javadoc.cfg create mode 100755 .kokoro/release/publish_javadoc.sh create mode 100644 .kokoro/release/stage.cfg create mode 100755 .kokoro/release/stage.sh create mode 100755 .kokoro/trampoline.sh diff --git a/.kokoro/build.sh b/.kokoro/build.sh new file mode 100755 index 000000000..dad012856 --- /dev/null +++ b/.kokoro/build.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright 2019 Google 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. + +set -eo pipefail + +cd github/api-common-java/ + +# Print out Java +java -version +echo $JOB_TYPE + +./gradlew assemble +./gradlew build install + +bash $KOKORO_GFILE_DIR/codecov.sh diff --git a/.kokoro/common.cfg b/.kokoro/common.cfg new file mode 100644 index 000000000..f10834a28 --- /dev/null +++ b/.kokoro/common.cfg @@ -0,0 +1,13 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Download trampoline resources. These will be in ${KOKORO_GFILE_DIR} +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# All builds use the trampoline script to run in docker. +build_file: "api-common-java/.kokoro/trampoline.sh" + +# Tell the trampoline which build file to use. +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/build.sh" +} diff --git a/.kokoro/continuous/common.cfg b/.kokoro/continuous/common.cfg new file mode 100644 index 000000000..1c23fa586 --- /dev/null +++ b/.kokoro/continuous/common.cfg @@ -0,0 +1,24 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "api-common-java/.kokoro/trampoline.sh" + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/build.sh" +} + +env_vars: { + key: "JOB_TYPE" + value: "test" +} diff --git a/.kokoro/continuous/java11.cfg b/.kokoro/continuous/java11.cfg new file mode 100644 index 000000000..709f2b4c7 --- /dev/null +++ b/.kokoro/continuous/java11.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java11" +} diff --git a/.kokoro/continuous/java7.cfg b/.kokoro/continuous/java7.cfg new file mode 100644 index 000000000..cb24f44ee --- /dev/null +++ b/.kokoro/continuous/java7.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java7" +} diff --git a/.kokoro/continuous/java8.cfg b/.kokoro/continuous/java8.cfg new file mode 100644 index 000000000..3b017fc80 --- /dev/null +++ b/.kokoro/continuous/java8.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java8" +} diff --git a/.kokoro/presubmit/common.cfg b/.kokoro/presubmit/common.cfg new file mode 100644 index 000000000..1c23fa586 --- /dev/null +++ b/.kokoro/presubmit/common.cfg @@ -0,0 +1,24 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "api-common-java/.kokoro/trampoline.sh" + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/build.sh" +} + +env_vars: { + key: "JOB_TYPE" + value: "test" +} diff --git a/.kokoro/presubmit/java11.cfg b/.kokoro/presubmit/java11.cfg new file mode 100644 index 000000000..709f2b4c7 --- /dev/null +++ b/.kokoro/presubmit/java11.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java11" +} diff --git a/.kokoro/presubmit/java7.cfg b/.kokoro/presubmit/java7.cfg new file mode 100644 index 000000000..cb24f44ee --- /dev/null +++ b/.kokoro/presubmit/java7.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java7" +} diff --git a/.kokoro/presubmit/java8.cfg b/.kokoro/presubmit/java8.cfg new file mode 100644 index 000000000..3b017fc80 --- /dev/null +++ b/.kokoro/presubmit/java8.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java8" +} diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg new file mode 100644 index 000000000..c22ce6933 --- /dev/null +++ b/.kokoro/release/common.cfg @@ -0,0 +1,49 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "api-common-java/.kokoro/trampoline.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/java8" +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 70247 + keyname: "maven-gpg-keyring" + } + } +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 70247 + keyname: "maven-gpg-passphrase" + } + } +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 70247 + keyname: "maven-gpg-pubkeyring" + } + } +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 70247 + keyname: "sonatype-credentials" + } + } +} diff --git a/.kokoro/release/common.sh b/.kokoro/release/common.sh new file mode 100755 index 000000000..d5738d430 --- /dev/null +++ b/.kokoro/release/common.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Copyright 2019 Google 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. + +set -eo pipefail + +# Get secrets from keystore and set and environment variables +setup_environment_secrets() { + export GPG_PASSPHRASE=$(cat ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-passphrase) + export GPG_TTY=$(tty) + export GPG_HOMEDIR=/gpg + mkdir $GPG_HOMEDIR + mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-pubkeyring $GPG_HOMEDIR/pubring.gpg + mv ${KOKORO_KEYSTORE_DIR}/70247_maven-gpg-keyring $GPG_HOMEDIR/secring.gpg + export GPG_KEY_ID=$(echo -n $(gpg --with-colons ${GPG_HOMEDIR}/pubring.gpg | awk -F':' '/pub/{ print $5 }')) + export SONATYPE_USERNAME=$(cat ${KOKORO_KEYSTORE_DIR}/70247_sonatype-credentials | cut -f1 -d'|') + export SONATYPE_PASSWORD=$(cat ${KOKORO_KEYSTORE_DIR}/70247_sonatype-credentials | cut -f2 -d'|') +} + +create_gradle_properties_file() { + echo " +signing.gnupg.executable=gpg +signing.gnupg.homeDir=${GPG_HOMEDIR} +signing.gnupg.keyName=${GPG_KEY_ID} +signing.gnupg.passphrase=${GPG_PASSPHRASE} + +ossrhUsername=${SONATYPE_USERNAME} +ossrhPassword=${SONATYPE_PASSWORD}" > $1 +} diff --git a/.kokoro/release/drop.cfg b/.kokoro/release/drop.cfg new file mode 100644 index 000000000..feb878972 --- /dev/null +++ b/.kokoro/release/drop.cfg @@ -0,0 +1,5 @@ +# Format: //devtools/kokoro/config/proto/build.proto + env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/release/drop.sh" +} diff --git a/.kokoro/release/drop.sh b/.kokoro/release/drop.sh new file mode 100755 index 000000000..ce6ae7543 --- /dev/null +++ b/.kokoro/release/drop.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Copyright 2019 Google 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. + +set -eo pipefail + +echo "This dropping a staged repo does not appear supported by the gradle-nexus-staging-plugin" +exit 1 diff --git a/.kokoro/release/promote.cfg b/.kokoro/release/promote.cfg new file mode 100644 index 000000000..d69f1d6f2 --- /dev/null +++ b/.kokoro/release/promote.cfg @@ -0,0 +1,5 @@ +# Format: //devtools/kokoro/config/proto/build.proto + env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/release/promote.sh" +} diff --git a/.kokoro/release/promote.sh b/.kokoro/release/promote.sh new file mode 100755 index 000000000..2dcfd256d --- /dev/null +++ b/.kokoro/release/promote.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Copyright 2019 Google 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. + +set -eo pipefail + +# STAGING_REPOSITORY_ID must be set +#if [ -z "${STAGING_REPOSITORY_ID}" ]; then +# echo "Missing STAGING_REPOSITORY_ID environment variable" +# exit 1 +#fi + +source $(dirname "$0")/common.sh +pushd $(dirname "$0")/../../ + +setup_environment_secrets +mkdir -p ${HOME}/.gradle +create_gradle_properties_file "${HOME}/.gradle/gradle.properties" + +./gradlew closeAndReleaseRepository diff --git a/.kokoro/release/publish_javadoc.cfg b/.kokoro/release/publish_javadoc.cfg new file mode 100644 index 000000000..043f92f13 --- /dev/null +++ b/.kokoro/release/publish_javadoc.cfg @@ -0,0 +1,19 @@ +# Format: //devtools/kokoro/config/proto/build.proto +env_vars: { + key: "STAGING_BUCKET" + value: "docs-staging" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/release/publish_javadoc.sh" +} + +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "docuploader_service_account" + } + } +} diff --git a/.kokoro/release/publish_javadoc.sh b/.kokoro/release/publish_javadoc.sh new file mode 100755 index 000000000..09e830704 --- /dev/null +++ b/.kokoro/release/publish_javadoc.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Copyright 2019 Google 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. + +set -eo pipefail + +if [[ -z "${CREDENTIALS}" ]]; then + CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account +fi + +if [[ -z "${STAGING_BUCKET}" ]]; then + echo "Need to set STAGING_BUCKET environment variable" + exit 1 +fi + +# work from the git root directory +pushd $(dirname "$0")/../../ + +# install docuploader package +python3 -m pip install gcp-docuploader + +NAME=api-common +VERSION=$(grep ${NAME}: versions.txt | cut -d: -f3) + +# build the docs +./gradlew javadocCombined + +pushd tmp_docs + +# create metadata +python3 -m docuploader create-metadata \ + --name ${NAME} \ + --version ${VERSION} \ + --language java + +# upload docs +python3 -m docuploader upload . \ + --credentials ${CREDENTIALS} \ + --staging-bucket ${STAGING_BUCKET} + +popd diff --git a/.kokoro/release/stage.cfg b/.kokoro/release/stage.cfg new file mode 100644 index 000000000..4016988c9 --- /dev/null +++ b/.kokoro/release/stage.cfg @@ -0,0 +1,35 @@ +# Format: //devtools/kokoro/config/proto/build.proto + env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/api-common-java/.kokoro/release/stage.sh" +} + +# Fetch the token needed for reporting release status to GitHub +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "yoshi-automation-github-key" + } + } +} + +# Fetch magictoken to use with Magic Github Proxy +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "releasetool-magictoken" + } + } +} + +# Fetch api key to use with Magic Github Proxy +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "magic-github-proxy-api-key" + } + } +} diff --git a/.kokoro/release/stage.sh b/.kokoro/release/stage.sh new file mode 100755 index 000000000..f927ce143 --- /dev/null +++ b/.kokoro/release/stage.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Copyright 2019 Google 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. + +set -eo pipefail + +if [[ -n "${AUTORELEASE_PR}" ]] +then + # Start the releasetool reporter + python3 -m pip install gcp-releasetool + python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script +fi + +source $(dirname "$0")/common.sh +MAVEN_SETTINGS_FILE=$(realpath $(dirname "$0")/../../)/settings.xml +pushd $(dirname "$0")/../../ + +setup_environment_secrets +mkdir -p ${HOME}/.gradle +create_gradle_properties_file "${HOME}/.gradle/gradle.properties" + +./gradlew assemble uploadArchives + +if [[ -n "${AUTORELEASE_PR}" ]] +then + ./gradlew closeAndReleaseRepository +fi diff --git a/.kokoro/trampoline.sh b/.kokoro/trampoline.sh new file mode 100755 index 000000000..3e90f6494 --- /dev/null +++ b/.kokoro/trampoline.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright 2019 Google 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. +set -eo pipefail +# Always run the cleanup script, regardless of the success of bouncing into +# the container. +function cleanup() { + chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + echo "cleanup"; +} +trap cleanup EXIT +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" diff --git a/build.gradle b/build.gradle index 0178b4dbb..5aa3faa66 100644 --- a/build.gradle +++ b/build.gradle @@ -190,6 +190,9 @@ artifacts { signing { required { gradle.taskGraph.hasTask("uploadArchives") } + if (project.hasProperty('signing.gnupg.executable')) { + useGpgCmd() + } sign configurations.archives } From d75a5ec9bdc60a5712cfdad8472357ce3bc1fefb Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Wed, 11 Dec 2019 18:36:06 -0500 Subject: [PATCH 48/66] update autovalue (#103) * update autovalue * auto value requires Java 8 compiler * give auto_value_annotations compile scope --- .travis.yml | 2 +- build.gradle | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 058f65fc9..2e07a726e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ addons: hostname: fake-hostname-to-work-around-travis-bug jdk: - oraclejdk8 - - openjdk7 + - openjdk8 script: ./gradlew check --info after_success: diff --git a/build.gradle b/build.gradle index 5aa3faa66..29d33638a 100644 --- a/build.gradle +++ b/build.gradle @@ -39,9 +39,10 @@ ext { // Shortcuts for libraries we are using libraries = [ javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', + auto_value_annotations: 'com.google.auto.value:auto-value-annotations:1.7', + auto_value: 'com.google.auto.value:auto-value:1.7', guava: 'com.google.guava:guava:28.1-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', - autovalue: 'com.google.auto.value:auto-value:1.1', error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.3', // Testing @@ -57,12 +58,15 @@ repositories { } dependencies { + + annotationProcessor libraries.auto_value + compile libraries.guava, libraries.jsr305, - libraries.javax_annotations + libraries.javax_annotations, + libraries.auto_value_annotations - compileOnly libraries.autovalue, - libraries.error_prone_annotations + compileOnly libraries.error_prone_annotations testCompile libraries.junit, libraries.mockito, @@ -134,9 +138,8 @@ configurations { } dependencies { - codeGeneration libraries.autovalue, libraries.jsr305 - compile libraries.jsr305 - compileOnly libraries.autovalue + codeGeneration libraries.auto_value, libraries.jsr305 + compile libraries.jsr305, libraries.auto_value_annotations } compileJava.classpath += configurations.codeGeneration From d0b55ad679e567d0b2542589ac8a586c24dc84c1 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 19 Feb 2020 22:28:38 +0100 Subject: [PATCH 49/66] Update dependency junit:junit to v4.13 (#105) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 29d33638a..ebfc6d56b 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,7 @@ ext { error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.3', // Testing - junit: 'junit:junit:4.12', + junit: 'junit:junit:4.13', mockito: 'org.mockito:mockito-core:1.10.19', truth: 'com.google.truth:truth:1.0', ] From 4aef9ae7ccedc8e57bdc970b7b303b39a204507d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Wed, 19 Feb 2020 22:29:55 +0100 Subject: [PATCH 50/66] Update dependency com.google.truth:truth to v1.0.1 (#107) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ebfc6d56b..b12fbf172 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ ext { // Testing junit: 'junit:junit:4.13', mockito: 'org.mockito:mockito-core:1.10.19', - truth: 'com.google.truth:truth:1.0', + truth: 'com.google.truth:truth:1.0.1', ] } From 77bf911f0aaab0f5d1dd556a65448cba3f58732c Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Wed, 19 Feb 2020 16:38:23 -0500 Subject: [PATCH 51/66] deps: update guava to 28.2 (#110) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b12fbf172..295c2be43 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ ext { javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', auto_value_annotations: 'com.google.auto.value:auto-value-annotations:1.7', auto_value: 'com.google.auto.value:auto-value:1.7', - guava: 'com.google.guava:guava:28.1-android', + guava: 'com.google.guava:guava:28.2-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.3', From 594e8b91a05131ea380ca8a4ae40f78745de83e0 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 24 Feb 2020 16:18:13 +0100 Subject: [PATCH 52/66] chore(deps): update dependency com.google.errorprone:error_prone_annotations to v2.3.4 (#111) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 295c2be43..24e5b0af7 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ ext { auto_value: 'com.google.auto.value:auto-value:1.7', guava: 'com.google.guava:guava:28.2-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', - error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.3', + error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.4', // Testing junit: 'junit:junit:4.13', From 95ef38c5d68794c58e89457b387e915b39e2a5d0 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 24 Feb 2020 16:18:56 +0100 Subject: [PATCH 53/66] Update dependency io.codearte.gradle.nexus:gradle-nexus-staging-plugin to v0.21.2 (#104) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 24e5b0af7..e7220b02b 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { } dependencies { classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.6", - "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.8.0" + "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.21.2" } } From 923f56a1f893e1cb3904d11f1285b64c31a01f56 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Fri, 28 Feb 2020 21:23:29 +0100 Subject: [PATCH 54/66] chore(deps): update dependency gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin to v0.8 (#109) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e7220b02b..b4e410765 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.6", + classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.8", "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.21.2" } } From a46286f3727035a553323d871b0e52e8cb5a18e4 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Fri, 13 Mar 2020 11:27:31 -0700 Subject: [PATCH 55/66] feat: add @BetaApi ApiFutures.catchingAsync (#117) Add new ApiFuture.catchingAsync method mirroring ApiFuture.catching, except allowing for an ApiAsyncFunction to be passed instead of ApiFunction. I tried to match the existing signature that allows the callback to return '? extends V' but this does not seem to be possible (see transformAsync). --- .../java/com/google/api/core/ApiFutures.java | 21 +++++++++++++++++++ .../com/google/api/core/ApiFuturesTest.java | 18 ++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/main/java/com/google/api/core/ApiFutures.java b/src/main/java/com/google/api/core/ApiFutures.java index d0638f1f6..d02892619 100644 --- a/src/main/java/com/google/api/core/ApiFutures.java +++ b/src/main/java/com/google/api/core/ApiFutures.java @@ -104,6 +104,27 @@ public static ApiFuture catching( return new ListenableFutureToApiFuture(catchingFuture); } + @BetaApi + public static ApiFuture catchingAsync( + ApiFuture input, + Class exceptionType, + final ApiAsyncFunction callback, + Executor executor) { + ListenableFuture catchingFuture = + Futures.catchingAsync( + listenableFutureForApiFuture(input), + exceptionType, + new AsyncFunction() { + @Override + public ListenableFuture apply(X exception) throws Exception { + ApiFuture result = callback.apply(exception); + return listenableFutureForApiFuture(result); + } + }, + executor); + return new ListenableFutureToApiFuture<>(catchingFuture); + } + public static ApiFuture immediateFuture(V value) { return new ListenableFutureToApiFuture<>(Futures.immediateFuture(value)); } diff --git a/src/test/java/com/google/api/core/ApiFuturesTest.java b/src/test/java/com/google/api/core/ApiFuturesTest.java index eec0fd8f3..de10b7ee8 100644 --- a/src/test/java/com/google/api/core/ApiFuturesTest.java +++ b/src/test/java/com/google/api/core/ApiFuturesTest.java @@ -83,6 +83,24 @@ public Integer apply(Exception ex) { assertThat(fallback.get()).isEqualTo(42); } + @Test + public void testCatchAsync() throws Exception { + SettableApiFuture future = SettableApiFuture.create(); + ApiFuture fallback = + ApiFutures.catchingAsync( + future, + Exception.class, + new ApiAsyncFunction() { + @Override + public ApiFuture apply(Exception ex) { + return ApiFutures.immediateFuture(42); + } + }, + directExecutor()); + future.setException(new Exception()); + assertThat(fallback.get()).isEqualTo(42); + } + @Test public void testTransform() throws Exception { SettableApiFuture inputFuture = SettableApiFuture.create(); From 4ae608319df29fa09d6de7513b837109541e683f Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Wed, 25 Mar 2020 14:14:21 -0700 Subject: [PATCH 56/66] chore: release v1.9.0 (#118) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 4df9f77d1..f8e233b27 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.8.2-SNAPSHOT +1.9.0 From bf82bae229ca87438718480f0e0526520af751f9 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 6 May 2020 00:17:46 +0000 Subject: [PATCH 57/66] chore: support complex resource identifiers --- .../google/api/pathtemplate/PathTemplate.java | 331 ++++++++++++------ .../api/pathtemplate/PathTemplateTest.java | 251 ++++++++++++- 2 files changed, 466 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index e21becd23..6b249ef06 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -40,6 +40,7 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -139,12 +140,13 @@ public class PathTemplate { // A splitter on slash. private static final Splitter SLASH_SPLITTER = Splitter.on('/').trimResults(); + // A regex to match the valid complex resource ID delimiters. + private static final Pattern DELIMITER_PATTERN = Pattern.compile("(_|\\-|\\.|~)"); + // Helper Types // ============ - /** - * Specifies a path segment kind. - */ + /** Specifies a path segment kind. */ enum SegmentKind { /** A literal path segment. */ LITERAL, @@ -165,37 +167,34 @@ enum SegmentKind { END_BINDING, } - /** - * Specifies a path segment. - */ + /** Specifies a path segment. */ @AutoValue abstract static class Segment { - /** - * A constant for the WILDCARD segment. - */ + /** A constant for the WILDCARD segment. */ private static final Segment WILDCARD = create(SegmentKind.WILDCARD, "*"); - /** - * A constant for the PATH_WILDCARD segment. - */ + /** A constant for the PATH_WILDCARD segment. */ private static final Segment PATH_WILDCARD = create(SegmentKind.PATH_WILDCARD, "**"); - /** - * A constant for the END_BINDING segment. - */ + /** A constant for the END_BINDING segment. */ private static final Segment END_BINDING = create(SegmentKind.END_BINDING, ""); - /** - * Creates a segment of given kind and value. - */ + /** Creates a segment of given kind and value. */ private static Segment create(SegmentKind kind, String value) { - return new AutoValue_PathTemplate_Segment(kind, value); + return new AutoValue_PathTemplate_Segment(kind, value, ""); } - /** - * The path segment kind. - */ + private static Segment wildcardCreate(String complexSeparator) { + return new AutoValue_PathTemplate_Segment( + SegmentKind.WILDCARD, + "*", + !complexSeparator.isEmpty() && DELIMITER_PATTERN.matcher(complexSeparator).find() + ? complexSeparator + : ""); + } + + /** The path segment kind. */ abstract SegmentKind kind(); /** @@ -204,9 +203,9 @@ private static Segment create(SegmentKind kind, String value) { */ abstract String value(); - /** - * Returns true of this segment is one of the wildcards, - */ + abstract String complexSeparator(); + + /** Returns true of this segment is one of the wildcards, */ boolean isAnyWildcard() { return kind() == SegmentKind.WILDCARD || kind() == SegmentKind.PATH_WILDCARD; } @@ -277,9 +276,7 @@ private PathTemplate(Iterable segments, boolean urlEncoding) { this.urlEncoding = urlEncoding; } - /** - * Returns the set of variable names used in the template. - */ + /** Returns the set of variable names used in the template. */ public Set vars() { return bindings.keySet(); } @@ -363,16 +360,12 @@ public PathTemplate subTemplate(String varName) { String.format("Variable '%s' is undefined in template '%s'", varName, this.toRawString())); } - /** - * Returns true of this template ends with a literal. - */ + /** Returns true of this template ends with a literal. */ public boolean endsWithLiteral() { return segments.get(segments.size() - 1).kind() == SegmentKind.LITERAL; } - /** - * Returns true of this template ends with a custom verb. - */ + /** Returns true of this template ends with a custom verb. */ public boolean endsWithCustomVerb() { return segments.get(segments.size() - 1).kind() == SegmentKind.CUSTOM_VERB; } @@ -464,9 +457,7 @@ public Map validatedMatch(String path, String exceptionMessagePr return matchMap; } - /** - * Returns true if the template matches the path. - */ + /** Returns true if the template matches the path. */ public boolean matches(String path) { return match(path) != null; } @@ -565,7 +556,8 @@ private Map match(String path, boolean forceHostName) { return ImmutableMap.copyOf(values); } - // Aligns input to start of literal value of literal or binding segment if input contains hostname. + // Aligns input to start of literal value of literal or binding segment if input contains + // hostname. private int alignInputToAlignableSegment(List input, int inPos, Segment segment) { switch (segment.kind()) { case BINDING: @@ -597,6 +589,7 @@ private boolean match( int segPos, Map values) { String currentVar = null; + List modifableInput = new ArrayList<>(input); while (segPos < segments.size()) { Segment seg = segments.get(segPos++); switch (seg.kind()) { @@ -614,18 +607,30 @@ private boolean match( break; case LITERAL: case WILDCARD: - if (inPos >= input.size()) { + if (inPos >= modifableInput.size()) { // End of input return false; } // Check literal match. - String next = decodeUrl(input.get(inPos++)); + String next = decodeUrl(modifableInput.get(inPos++)); if (seg.kind() == SegmentKind.LITERAL) { if (!seg.value().equals(next)) { // Literal does not match. return false; } } + if (seg.kind() == SegmentKind.WILDCARD && !seg.complexSeparator().isEmpty()) { + // Parse the complex resource separators one by one. + int complexSeparatorIndex = next.indexOf(seg.complexSeparator()); + if (complexSeparatorIndex >= 0) { + modifableInput.add(inPos, next.substring(complexSeparatorIndex + 1)); + next = next.substring(0, complexSeparatorIndex); + modifableInput.set(inPos - 1, next); + } else { + // No coplex resource ID separator found in the literal when we expected one. + return false; + } + } if (currentVar != null) { // Create or extend current match values.put(currentVar, concatCaptures(values.get(currentVar), next)); @@ -645,18 +650,19 @@ private boolean match( segsToMatch++; } } - int available = (input.size() - inPos) - segsToMatch; + int available = (modifableInput.size() - inPos) - segsToMatch; // If this segment is empty, make sure it is still captured. if (available == 0 && !values.containsKey(currentVar)) { values.put(currentVar, ""); } while (available-- > 0) { values.put( - currentVar, concatCaptures(values.get(currentVar), decodeUrl(input.get(inPos++)))); + currentVar, + concatCaptures(values.get(currentVar), decodeUrl(modifableInput.get(inPos++)))); } } } - return inPos == input.size(); + return inPos == modifableInput.size(); } private static String concatCaptures(@Nullable String cur, String next) { @@ -681,9 +687,7 @@ public String instantiate(Map values) { return instantiate(values, false); } - /** - * Shortcut for {@link #instantiate(Map)} with a vararg parameter for keys and values. - */ + /** Shortcut for {@link #instantiate(Map)} with a vararg parameter for keys and values. */ public String instantiate(String... keysAndValues) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (int i = 0; i < keysAndValues.length; i += 2) { @@ -831,6 +835,7 @@ public List decode(String path) { // ================ private static ImmutableList parseTemplate(String template) { + System.out.println("DEL: PARSING TEMPLATE"); // Skip useless leading slash. if (template.startsWith("/")) { template = template.substring(1); @@ -850,89 +855,120 @@ private static ImmutableList parseTemplate(String template) { int pathWildCardBound = 0; for (String seg : Splitter.on('/').trimResults().split(template)) { + if (DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() + || DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find()) { + throw new ValidationException("parse error: invalid begin or end character in '%s'", seg); + } + // Disallow zero or multiple delimiters between variable names. + if (Pattern.compile("\\}(_|\\-|\\.|~){2,}\\{").matcher(seg).find()) { + throw new ValidationException( + "parse error: two consecutive delimiter characters in '%s'", seg); + } // If segment starts with '{', a binding group starts. boolean bindingStarts = seg.startsWith("{"); boolean implicitWildcard = false; + boolean complexDelimiterFound = false; if (bindingStarts) { if (varName != null) { throw new ValidationException("parse error: nested binding in '%s'", template); } seg = seg.substring(1); - int i = seg.indexOf('='); - if (i <= 0) { - // Possibly looking at something like "{name}" with implicit wildcard. - if (seg.endsWith("}")) { - // Remember to add an implicit wildcard later. - implicitWildcard = true; - varName = seg.substring(0, seg.length() - 1).trim(); - seg = seg.substring(seg.length() - 1).trim(); - } else { - throw new ValidationException("parse error: invalid binding syntax in '%s'", template); - } - } else { - // Looking at something like "{name=wildcard}". - varName = seg.substring(0, i).trim(); - seg = seg.substring(i + 1).trim(); + // Check for invalid complex resource ID delimiters. + if (Pattern.compile("\\}[^_\\-\\.~]\\{").matcher(seg).find() + || Pattern.compile("\\}\\{").matcher(seg).find()) { + throw new ValidationException( + "parse error: missing or invalid complex resource ID delimiter character in '%s'", + seg); } - builder.add(Segment.create(SegmentKind.BINDING, varName)); - } - // If segment ends with '}', a binding group ends. Remove the brace and remember. - boolean bindingEnds = seg.endsWith("}"); - if (bindingEnds) { - seg = seg.substring(0, seg.length() - 1).trim(); - } + Matcher complexPatternDelimiterMatcher = + Pattern.compile("\\}(_|\\-|\\.|~){1}").matcher(seg); + complexDelimiterFound = complexPatternDelimiterMatcher.find(); - // Process the segment, after stripping off "{name=.." and "..}". - switch (seg) { - case "**": - case "*": - if ("**".equals(seg)) { - pathWildCardBound++; - } - Segment wildcard = seg.length() == 2 ? Segment.PATH_WILDCARD : Segment.WILDCARD; - if (varName == null) { - // Not in a binding, turn wildcard into implicit binding. - // "*" => "{$n=*}" - builder.add(Segment.create(SegmentKind.BINDING, "$" + freeWildcardCounter)); - freeWildcardCounter++; - builder.add(wildcard); - builder.add(Segment.END_BINDING); + // Look for complex resource names. + // Need to handles something like "{user_a}~{user_b}". + if (complexDelimiterFound) { + builder.addAll(parseComplexResourceId(seg)); + } else { + int i = seg.indexOf('='); + if (i <= 0) { + // Possibly looking at something like "{name}" with implicit wildcard. + if (seg.endsWith("}")) { + // Remember to add an implicit wildcard later. + implicitWildcard = true; + varName = seg.substring(0, seg.length() - 1).trim(); + seg = seg.substring(seg.length() - 1).trim(); + } else { + throw new ValidationException( + "parse error: invalid binding syntax in '%s'", template); + } } else { - builder.add(wildcard); - } - break; - case "": - if (!bindingEnds) { - throw new ValidationException( - "parse error: empty segment not allowed in '%s'", template); + // Looking at something like "{name=wildcard}". + varName = seg.substring(0, i).trim(); + seg = seg.substring(i + 1).trim(); } - // If the wildcard is implicit, seg will be empty. Just continue. - break; - default: - builder.add(Segment.create(SegmentKind.LITERAL, seg)); + builder.add(Segment.create(SegmentKind.BINDING, varName)); + } } - // End a binding. - if (bindingEnds) { - // Reset varName to null for next binding. - varName = null; + if (!complexDelimiterFound) { + // If segment ends with '}', a binding group ends. Remove the brace and remember. + boolean bindingEnds = seg.endsWith("}"); + if (bindingEnds) { + seg = seg.substring(0, seg.length() - 1).trim(); + } - if (implicitWildcard) { - // Looking at something like "{var}". Insert an implicit wildcard, as it is the same - // as "{var=*}". - builder.add(Segment.WILDCARD); + // Process the segment, after stripping off "{name=.." and "..}". + switch (seg) { + case "**": + case "*": + if ("**".equals(seg)) { + pathWildCardBound++; + } + Segment wildcard = seg.length() == 2 ? Segment.PATH_WILDCARD : Segment.WILDCARD; + if (varName == null) { + // Not in a binding, turn wildcard into implicit binding. + // "*" => "{$n=*}" + builder.add(Segment.create(SegmentKind.BINDING, "$" + freeWildcardCounter)); + freeWildcardCounter++; + builder.add(wildcard); + builder.add(Segment.END_BINDING); + } else { + builder.add(wildcard); + } + break; + case "": + if (!bindingEnds) { + throw new ValidationException( + "parse error: empty segment not allowed in '%s'", template); + } + // If the wildcard is implicit, seg will be empty. Just continue. + break; + default: + builder.add(Segment.create(SegmentKind.LITERAL, seg)); } - builder.add(Segment.END_BINDING); - } - if (pathWildCardBound > 1) { - // Report restriction on number of '**' in the pattern. There can be only one, which - // enables non-backtracking based matching. - throw new ValidationException( - "parse error: pattern must not contain more than one path wildcard ('**') in '%s'", - template); + // End a binding. + if (bindingEnds && !complexDelimiterFound) { + // Reset varName to null for next binding. + varName = null; + + if (implicitWildcard) { + // Looking at something like "{var}". Insert an implicit wildcard, as it is the same + // as "{var=*}". + builder.add(Segment.WILDCARD); + } + builder.add(Segment.END_BINDING); + } + + if (pathWildCardBound > 1) { + // Report restriction on number of '**' in the pattern. There can be only one, which + // enables non-backtracking based matching. + throw new ValidationException( + "parse error: pattern must not contain more than one path wildcard ('**') in '%s'", + template); + } } } @@ -942,6 +978,77 @@ private static ImmutableList parseTemplate(String template) { return builder.build(); } + private static List parseComplexResourceId(String seg) { + List segments = new ArrayList<>(); + List separatorIndices = new ArrayList<>(); + + Matcher complexPatternDelimiterMatcher = Pattern.compile("\\}(_|\\-|\\.|~){1}").matcher(seg); + boolean delimiterFound = complexPatternDelimiterMatcher.find(); + + while (delimiterFound) { + int delimiterIndex = complexPatternDelimiterMatcher.start(); + if (seg.substring(delimiterIndex).startsWith("}")) { + delimiterIndex += 1; + } + String currDelimiter = seg.substring(delimiterIndex, delimiterIndex + 1); + if (!DELIMITER_PATTERN.matcher(currDelimiter).find()) { + throw new ValidationException( + "parse error: invalid complex ID delimiter '%s' in '%s'", currDelimiter, seg); + } + separatorIndices.add(currDelimiter); + delimiterFound = complexPatternDelimiterMatcher.find(delimiterIndex + 1); + } + // The last entry does not have a delimiter. + separatorIndices.add(""); + + String subVarName = null; + Iterable complexSubsegments = + Splitter.onPattern("\\}[_\\-\\.~]").trimResults().split(seg); + boolean complexSegImplicitWildcard = false; + int currIteratorIndex = 0; + for (String complexSeg : complexSubsegments) { + boolean subsegmentBindingStarts = complexSeg.startsWith("{"); + if (subsegmentBindingStarts) { + if (subVarName != null) { + throw new ValidationException("parse error: nested binding in '%s'", complexSeg); + } + complexSeg = complexSeg.substring(1); + } + subVarName = complexSeg.trim(); + + boolean subBindingEnds = complexSeg.endsWith("}"); + int i = complexSeg.indexOf('='); + if (i <= 0) { + // Possibly looking at something like "{name}" with implicit wildcard. + if (subBindingEnds) { + // Remember to add an implicit wildcard later. + complexSegImplicitWildcard = true; + subVarName = complexSeg.substring(0, complexSeg.length() - 1).trim(); + complexSeg = complexSeg.substring(complexSeg.length() - 1).trim(); + } + } else { + // Looking at something like "{name=wildcard}". + subVarName = complexSeg.substring(0, i).trim(); + complexSeg = complexSeg.substring(i + 1).trim(); + if (complexSeg.equals("**")) { + throw new ValidationException( + "parse error: wildcard path not allowed in complex ID resource '%s'", subVarName); + } + } + String complexDelimiter = + currIteratorIndex < separatorIndices.size() + ? separatorIndices.get(currIteratorIndex) + : ""; + segments.add(Segment.create(SegmentKind.BINDING, subVarName)); + segments.add(Segment.wildcardCreate(complexDelimiter)); + segments.add(Segment.END_BINDING); + subVarName = null; + + currIteratorIndex++; + } + return segments; + } + // Helpers // ======= @@ -1003,9 +1110,7 @@ private static void restore(ListIterator segments, int index) { // Equality and String Conversion // ============================== - /** - * Returns a pretty version of the template as a string. - */ + /** Returns a pretty version of the template as a string. */ @Override public String toString() { return toSyntax(segments, true); diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 45be25f3b..338ed3643 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -33,6 +33,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.truth.Truth; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Map; import org.junit.Rule; import org.junit.Test; @@ -40,9 +43,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests for {@link PathTemplate}. - */ +/** Tests for {@link PathTemplate}. */ @RunWith(JUnit4.class) public class PathTemplateTest { @@ -171,6 +172,250 @@ public void matchWithUnboundInMiddle() { assertPositionalMatch(template.match("bar/foo/foo/foo/bar"), "foo/foo", "bar"); } + // Complex Resource ID Segments. + // ======== + + @Test + public void complexResourceIdBasicCases() { + // Separate by "~". + PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone_a}~{zone_b}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c~us-east3-a"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a}~{zone_b")).isNull(); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEqualTo("us-east3-a"); + + // Separate by "-". + template = PathTemplate.create("projects/{project}/zones/{zone_a}-{zone_b}"); + match = template.match("projects/project-123/zones/europe-west3-c~us-east3-a"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe"); + Truth.assertThat(match.get("zone_b")).isEqualTo("west3-c~us-east3-a"); + + // Separate by ".". + template = PathTemplate.create("projects/{project}/zones/{zone_a}.{zone_b}"); + match = template.match("projects/project-123/zones/europe-west3-c.us-east3-a"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEqualTo("us-east3-a"); + + // Separate by "_". + template = PathTemplate.create("projects/{project}/zones/{zone_a}_{zone_b}"); + match = template.match("projects/project-123/zones/europe-west3-c_us-east3-a"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEqualTo("us-east3-a"); + } + + @Test + public void complexResourceIdEqualsWildcard() { + PathTemplate template = PathTemplate.create("projects/{project=*}/zones/{zone_a=*}~{zone_b=*}"); + Map match = + template.match("projects/project-123/zones/europe-west3-c~us-east3-a"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a}~{zone_b")).isNull(); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEqualTo("us-east3-a"); + } + + @Test + public void complexResourceIdEqualsPathWildcard() { + thrown.expect(ValidationException.class); + PathTemplate template = PathTemplate.create("projects/{project=*}/zones/{zone_a=**}~{zone_b}"); + thrown.expectMessage( + String.format( + "parse error: wildcard path not allowed in complex ID resource '%s'", "zone_a")); + + template = PathTemplate.create("projects/{project=*}/zones/{zone_a}.{zone_b=**}"); + thrown.expectMessage( + String.format( + "parse error: wildcard path not allowed in complex ID resource '%s'", "zone_b")); + } + + @Test + public void complexResourceIdMissingMatches() { + PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone_a}~{zone_b}"); + Truth.assertThat(template.match("projects/project-123/zones/europe-west3-c")).isNull(); + + template = PathTemplate.create("projects/{project}/zones/{zone_a}~{zone_b}.{zone_c}"); + Map match = + template.match("projects/project-123/zones/europe-west3-c~.us-east3-a"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a}~{zone_b")).isNull(); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEmpty(); + Truth.assertThat(match.get("zone_c")).isEqualTo("us-east3-a"); + } + + @Test + public void complexResourceIdNoSeparator() { + thrown.expect(ValidationException.class); + PathTemplate.create("projects/{project}/zones/{zone_a}{zone_b}"); + thrown.expectMessage( + String.format( + "parse error: missing or invalid complex resource ID delimiter character in '%s'", + "{zone_a}{zone_b}")); + + PathTemplate.create("projects/{project}/zones/{zone_a}_{zone_b}{zone_c}"); + thrown.expectMessage( + String.format( + "parse error: missing or invalid complex resource ID delimiter character in '%s'", + "{zone_a}_{zone_b}{zone_c}")); + } + + @Test + public void complexResourceIdInvalidDelimiter() { + thrown.expect(ValidationException.class); + // Not a comprehensive set of invalid delimiters, please check the class's defined pattern. + List someInvalidDelimiters = + new ArrayList<>(Arrays.asList("|", "!", "@", "a", "1", ",", "{", ")")); + for (String invalidDelimiter : someInvalidDelimiters) { + PathTemplate.create( + String.format("projects/{project=*}/zones/{zone_a}%s{zone_b}", invalidDelimiter)); + thrown.expectMessage( + String.format( + "parse error: missing or invalid complex resource ID delimiter character in '%s'", + String.format("{zone_a}%s{zone_b}", invalidDelimiter))); + } + } + + @Test + public void complexResourceIdMixedSeparators() { + // Separate by a mix of delimiters. + PathTemplate template = + PathTemplate.create("projects/{project}/zones/{zone_a}~{zone_b}.{zone_c}-{zone_d}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c~us-east3-a.us-west2-b-europe-west2-b"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEqualTo("us-east3-a"); + Truth.assertThat(match.get("zone_c")).isEqualTo("us"); + Truth.assertThat(match.get("zone_d")).isEqualTo("west2-b-europe-west2-b"); + + template = PathTemplate.create("projects/{project}/zones/{zone_a}.{zone_b}.{zone_c}~{zone_d}"); + match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c.us-east3-a.us-west2-b~europe-west2-b"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe-west3-c"); + Truth.assertThat(match.get("zone_b")).isEqualTo("us-east3-a"); + Truth.assertThat(match.get("zone_c")).isEqualTo("us-west2-b"); + Truth.assertThat(match.get("zone_d")).isEqualTo("europe-west2-b"); + } + + @Test + public void complexResourceIdInParent() { + // One parent has a complex resource ID. + PathTemplate template = + PathTemplate.create( + "projects/{project}/zones/{zone_a}-{zone_b}_{zone_c}/machines/{machine}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c-us-east3-a_us-west2-b/machines/roomba"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe"); + Truth.assertThat(match.get("zone_b")).isEqualTo("west3-c-us-east3-a"); + Truth.assertThat(match.get("zone_c")).isEqualTo("us-west2-b"); + Truth.assertThat(match.get("machine")).isEqualTo("roomba"); + + // All parents and resource IDs have complex resource IDs. + template = + PathTemplate.create( + "projects/{foo}_{bar}/zones/{zone_a}-{zone_b}_{zone_c}/machines/{cell1}.{cell2}"); + match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project_123/zones/europe-west3-c-us-east3-a_us-west2-b/machines/roomba.broomba"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("foo")).isEqualTo("project"); + Truth.assertThat(match.get("bar")).isEqualTo("123"); + Truth.assertThat(match.get("zone_a")).isEqualTo("europe"); + Truth.assertThat(match.get("zone_b")).isEqualTo("west3-c-us-east3-a"); + Truth.assertThat(match.get("zone_c")).isEqualTo("us-west2-b"); + Truth.assertThat(match.get("cell1")).isEqualTo("roomba"); + Truth.assertThat(match.get("cell2")).isEqualTo("broomba"); + } + + @Test + public void complexResourceBasicInvalidIds() { + thrown.expect(ValidationException.class); + PathTemplate.create("projects/*/zones/~{zone_a}"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "~{zone_a}")); + + PathTemplate.create("projects/*/zones/{zone_a}~"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{zone_a}~")); + + PathTemplate.create("projects/*/zones/.{zone_a}"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", ".{zone_a}")); + + PathTemplate.create("projects/*/zones/{zone_a}."); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{zone_a}.")); + + PathTemplate.create("projects/*/zones/-{zone_a}"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "-{zone_a}")); + + PathTemplate.create("projects/*/zones/{zone_a}-"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{zone_a}-")); + + PathTemplate.create("projects/*/zones/_{zone_a}"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{zone_a}_")); + + PathTemplate.create("projects/*/zones/{zone_a}_"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{zone_a}_")); + } + + @Test + public void complexResourceMultipleDelimiters() { + thrown.expect(ValidationException.class); + PathTemplate.create("projects/*/zones/.-~{zone_a}"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", ".-~{zone_a}")); + + PathTemplate.create("projects/*/zones/{zone_a}~.{zone_b}"); + thrown.expectMessage( + String.format( + "parse error: two consecutive delimiter characters in '%s'", "{zone_a}~.{zone_b}")); + + PathTemplate.create("projects/*/zones/{zone_a}~{zone_b}..{zone_c}"); + thrown.expectMessage( + String.format( + "parse error: two consecutive delimiter characters in '%s'", + "{zone_a}~{zone_b}..{zone_c}")); + + String pathString = "projects/project_123/zones/lorum~ipsum"; + PathTemplate template = PathTemplate.create("projects/*/zones/{zone_.~-a}~{zone_b}"); + template.validate(pathString, ""); + // No assertion - success is no exception thrown from template.validate(). + Map match = template.match(pathString); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get("zone_.~-a")).isEqualTo("lorum"); + Truth.assertThat(match.get("zone_b")).isEqualTo("ipsum"); + } + // Validate // ======== From 4c68585e762917905ebd8ad143a3f0ebaac92c42 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 6 May 2020 00:38:24 +0000 Subject: [PATCH 58/66] remove debug printf --- .../google/api/pathtemplate/PathTemplate.java | 71 +++++++------------ 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 6b249ef06..d18ae118f 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -53,21 +53,18 @@ /** * Represents a path template. * - *

- * Templates use the syntax of the API platform; see the protobuf of HttpRule for details. A + *

Templates use the syntax of the API platform; see the protobuf of HttpRule for details. A * template consists of a sequence of literals, wildcards, and variable bindings, where each binding - * can have a sub-path. A string representation can be parsed into an instance of - * {@link PathTemplate}, which can then be used to perform matching and instantiation. + * can have a sub-path. A string representation can be parsed into an instance of {@link + * PathTemplate}, which can then be used to perform matching and instantiation. * - *

- * Matching and instantiation deals with unescaping and escaping using URL encoding rules. For - * example, if a template variable for a single segment is instantiated with a string like - * {@code "a/b"}, the slash will be escaped to {@code "%2f"}. (Note that slash will not be escaped - * for a multiple-segment variable, but other characters will). The literals in the template itself - * are not escaped automatically, and must be already URL encoded. + *

Matching and instantiation deals with unescaping and escaping using URL encoding rules. For + * example, if a template variable for a single segment is instantiated with a string like {@code + * "a/b"}, the slash will be escaped to {@code "%2f"}. (Note that slash will not be escaped for a + * multiple-segment variable, but other characters will). The literals in the template itself are + * not escaped automatically, and must be already URL encoded. * - *

- * Here is an example for a template using simple variables: + *

Here is an example for a template using simple variables: * *

  *   PathTemplate template = PathTemplate.create("v1/shelves/{shelf}/books/{book}");
@@ -125,9 +122,8 @@
 public class PathTemplate {
 
   /**
-   * A constant identifying the special variable used for endpoint bindings in the result of
-   * {@link #matchFromFullName(String)}. It may also contain protocol string, if its provided in the
-   * input.
+   * A constant identifying the special variable used for endpoint bindings in the result of {@link
+   * #matchFromFullName(String)}. It may also contain protocol string, if its provided in the input.
    */
   public static final String HOSTNAME_VAR = "$hostname";
 
@@ -333,11 +329,10 @@ public PathTemplate withoutVars() {
    * 
* * The returned template will never have named variables, but only wildcards, which are dealt with - * in matching and instantiation using '$n'-variables. See the documentation of - * {@link #match(String)} and {@link #instantiate(Map)}, respectively. + * in matching and instantiation using '$n'-variables. See the documentation of {@link + * #match(String)} and {@link #instantiate(Map)}, respectively. * - *

- * For a variable which has no sub-path, this returns a path template with a single wildcard + *

For a variable which has no sub-path, this returns a path template with a single wildcard * ('*'). * * @throws ValidationException if the variable does not exist in the template. @@ -403,9 +398,7 @@ public void validate(String path, String exceptionMessagePrefix) { throw new ValidationException( String.format( "%s: Parameter \"%s\" must be in the form \"%s\"", - exceptionMessagePrefix, - path, - this.toString())); + exceptionMessagePrefix, path, this.toString())); } } @@ -415,15 +408,12 @@ public void validate(String path, String exceptionMessagePrefix) { * throws a ValidationException. The exceptionMessagePrefix parameter will be prepended to the * ValidationException message. * - *

- * If the path starts with '//', the first segment will be interpreted as a host name and stored - * in the variable {@link #HOSTNAME_VAR}. + *

If the path starts with '//', the first segment will be interpreted as a host name and + * stored in the variable {@link #HOSTNAME_VAR}. * - *

- * See the {@link PathTemplate} class documentation for examples. + *

See the {@link PathTemplate} class documentation for examples. * - *

- * For free wildcards in the template, the matching process creates variables named '$n', where + *

For free wildcards in the template, the matching process creates variables named '$n', where * 'n' is the wildcard's position in the template (starting at n=0). For example: * *

@@ -450,9 +440,7 @@ public Map validatedMatch(String path, String exceptionMessagePr
       throw new ValidationException(
           String.format(
               "%s: Parameter \"%s\" must be in the form \"%s\"",
-              exceptionMessagePrefix,
-              path,
-              this.toString()));
+              exceptionMessagePrefix, path, this.toString()));
     }
     return matchMap;
   }
@@ -467,15 +455,12 @@ public boolean matches(String path) {
    * will be properly unescaped using URL encoding rules. If the path does not match the template,
    * null is returned.
    *
-   * 

- * If the path starts with '//', the first segment will be interpreted as a host name and stored - * in the variable {@link #HOSTNAME_VAR}. + *

If the path starts with '//', the first segment will be interpreted as a host name and + * stored in the variable {@link #HOSTNAME_VAR}. * - *

- * See the {@link PathTemplate} class documentation for examples. + *

See the {@link PathTemplate} class documentation for examples. * - *

- * For free wildcards in the template, the matching process creates variables named '$n', where + *

For free wildcards in the template, the matching process creates variables named '$n', where * 'n' is the wildcard's position in the template (starting at n=0). For example: * *

@@ -676,10 +661,9 @@ private static String concatCaptures(@Nullable String cur, String next) {
    * Instantiate the template based on the given variable assignment. Performs proper URL escaping
    * of variable assignments.
    *
-   * 

- * Note that free wildcards in the template must have bindings of '$n' variables, where 'n' is the - * position of the wildcard (starting at 0). See the documentation of {@link #match(String)} for - * details. + *

Note that free wildcards in the template must have bindings of '$n' variables, where 'n' is + * the position of the wildcard (starting at 0). See the documentation of {@link #match(String)} + * for details. * * @throws ValidationException if a variable occurs in the template without a binding. */ @@ -835,7 +819,6 @@ public List decode(String path) { // ================ private static ImmutableList parseTemplate(String template) { - System.out.println("DEL: PARSING TEMPLATE"); // Skip useless leading slash. if (template.startsWith("/")) { template = template.substring(1); From 7419e19b882bef5d2c467a9501fe4b3910eb7ba0 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Thu, 7 May 2020 19:12:26 +0000 Subject: [PATCH 59/66] fix: clean up PathTemplate.java and tests --- .../google/api/pathtemplate/PathTemplate.java | 131 +++++++++++------- .../api/pathtemplate/PathTemplateTest.java | 11 +- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index d18ae118f..970fa510e 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -53,18 +53,21 @@ /** * Represents a path template. * - *

Templates use the syntax of the API platform; see the protobuf of HttpRule for details. A + *

+ * Templates use the syntax of the API platform; see the protobuf of HttpRule for details. A * template consists of a sequence of literals, wildcards, and variable bindings, where each binding - * can have a sub-path. A string representation can be parsed into an instance of {@link - * PathTemplate}, which can then be used to perform matching and instantiation. + * can have a sub-path. A string representation can be parsed into an instance of + * {@link PathTemplate}, which can then be used to perform matching and instantiation. * - *

Matching and instantiation deals with unescaping and escaping using URL encoding rules. For - * example, if a template variable for a single segment is instantiated with a string like {@code - * "a/b"}, the slash will be escaped to {@code "%2f"}. (Note that slash will not be escaped for a - * multiple-segment variable, but other characters will). The literals in the template itself are - * not escaped automatically, and must be already URL encoded. + *

+ * Matching and instantiation deals with unescaping and escaping using URL encoding rules. For + * example, if a template variable for a single segment is instantiated with a string like + * {@code "a/b"}, the slash will be escaped to {@code "%2f"}. (Note that slash will not be escaped + * for a multiple-segment variable, but other characters will). The literals in the template itself + * are not escaped automatically, and must be already URL encoded. * - *

Here is an example for a template using simple variables: + *

+ * Here is an example for a template using simple variables: * *

  *   PathTemplate template = PathTemplate.create("v1/shelves/{shelf}/books/{book}");
@@ -122,8 +125,9 @@
 public class PathTemplate {
 
   /**
-   * A constant identifying the special variable used for endpoint bindings in the result of {@link
-   * #matchFromFullName(String)}. It may also contain protocol string, if its provided in the input.
+   * A constant identifying the special variable used for endpoint bindings in the result of
+   * {@link #matchFromFullName(String)}. It may also contain protocol string, if its provided in the
+   * input.
    */
   public static final String HOSTNAME_VAR = "$hostname";
 
@@ -137,7 +141,22 @@ public class PathTemplate {
   private static final Splitter SLASH_SPLITTER = Splitter.on('/').trimResults();
 
   // A regex to match the valid complex resource ID delimiters.
-  private static final Pattern DELIMITER_PATTERN = Pattern.compile("(_|\\-|\\.|~)");
+  private static final Pattern COMPLEX_DELIMITER_PATTERN = Pattern.compile("[_\\-\\.~]");
+
+  // A regex to match multiple complex resource ID delimiters.
+  private static final Pattern MULTIPLE_COMPLEX_DELIMITER_PATTERN =
+      Pattern.compile("\\}[_\\-\\.~]{2,}\\{");
+
+  // A regex to match a missing complex resource ID delimiter.
+  private static final Pattern MISSING_COMPLEX_DELIMITER_PATTERN = Pattern.compile("\\}\\{");
+
+  // A regex to match invalid complex resource ID delimiters.
+  private static final Pattern INVALID_COMPLEX_DELIMITER_PATTERN =
+      Pattern.compile("\\}[^_\\-\\.~]\\{");
+
+  // A regex to match a closing segment (end brace) followed by one complex resource ID delimiter.
+  private static final Pattern END_SEGMENT_COMPLEX_DELIMITER_PATTERN =
+      Pattern.compile("\\}[_\\-\\.~]{1}");
 
   // Helper Types
   // ============
@@ -185,7 +204,7 @@ private static Segment wildcardCreate(String complexSeparator) {
       return new AutoValue_PathTemplate_Segment(
           SegmentKind.WILDCARD,
           "*",
-          !complexSeparator.isEmpty() && DELIMITER_PATTERN.matcher(complexSeparator).find()
+          !complexSeparator.isEmpty() && COMPLEX_DELIMITER_PATTERN.matcher(complexSeparator).find()
               ? complexSeparator
               : "");
     }
@@ -329,10 +348,11 @@ public PathTemplate withoutVars() {
    * 
* * The returned template will never have named variables, but only wildcards, which are dealt with - * in matching and instantiation using '$n'-variables. See the documentation of {@link - * #match(String)} and {@link #instantiate(Map)}, respectively. + * in matching and instantiation using '$n'-variables. See the documentation of + * {@link #match(String)} and {@link #instantiate(Map)}, respectively. * - *

For a variable which has no sub-path, this returns a path template with a single wildcard + *

+ * For a variable which has no sub-path, this returns a path template with a single wildcard * ('*'). * * @throws ValidationException if the variable does not exist in the template. @@ -398,7 +418,9 @@ public void validate(String path, String exceptionMessagePrefix) { throw new ValidationException( String.format( "%s: Parameter \"%s\" must be in the form \"%s\"", - exceptionMessagePrefix, path, this.toString())); + exceptionMessagePrefix, + path, + this.toString())); } } @@ -408,12 +430,15 @@ public void validate(String path, String exceptionMessagePrefix) { * throws a ValidationException. The exceptionMessagePrefix parameter will be prepended to the * ValidationException message. * - *

If the path starts with '//', the first segment will be interpreted as a host name and - * stored in the variable {@link #HOSTNAME_VAR}. + *

+ * If the path starts with '//', the first segment will be interpreted as a host name and stored + * in the variable {@link #HOSTNAME_VAR}. * - *

See the {@link PathTemplate} class documentation for examples. + *

+ * See the {@link PathTemplate} class documentation for examples. * - *

For free wildcards in the template, the matching process creates variables named '$n', where + *

+ * For free wildcards in the template, the matching process creates variables named '$n', where * 'n' is the wildcard's position in the template (starting at n=0). For example: * *

@@ -440,7 +465,9 @@ public Map validatedMatch(String path, String exceptionMessagePr
       throw new ValidationException(
           String.format(
               "%s: Parameter \"%s\" must be in the form \"%s\"",
-              exceptionMessagePrefix, path, this.toString()));
+              exceptionMessagePrefix,
+              path,
+              this.toString()));
     }
     return matchMap;
   }
@@ -455,12 +482,15 @@ public boolean matches(String path) {
    * will be properly unescaped using URL encoding rules. If the path does not match the template,
    * null is returned.
    *
-   * 

If the path starts with '//', the first segment will be interpreted as a host name and - * stored in the variable {@link #HOSTNAME_VAR}. + *

+ * If the path starts with '//', the first segment will be interpreted as a host name and stored + * in the variable {@link #HOSTNAME_VAR}. * - *

See the {@link PathTemplate} class documentation for examples. + *

+ * See the {@link PathTemplate} class documentation for examples. * - *

For free wildcards in the template, the matching process creates variables named '$n', where + *

+ * For free wildcards in the template, the matching process creates variables named '$n', where * 'n' is the wildcard's position in the template (starting at n=0). For example: * *

@@ -574,7 +604,7 @@ private boolean match(
       int segPos,
       Map values) {
     String currentVar = null;
-    List modifableInput = new ArrayList<>(input);
+    List modifiableInput = new ArrayList<>(input);
     while (segPos < segments.size()) {
       Segment seg = segments.get(segPos++);
       switch (seg.kind()) {
@@ -592,12 +622,12 @@ private boolean match(
           break;
         case LITERAL:
         case WILDCARD:
-          if (inPos >= modifableInput.size()) {
+          if (inPos >= modifiableInput.size()) {
             // End of input
             return false;
           }
           // Check literal match.
-          String next = decodeUrl(modifableInput.get(inPos++));
+          String next = decodeUrl(modifiableInput.get(inPos++));
           if (seg.kind() == SegmentKind.LITERAL) {
             if (!seg.value().equals(next)) {
               // Literal does not match.
@@ -608,11 +638,11 @@ private boolean match(
             // Parse the complex resource separators one by one.
             int complexSeparatorIndex = next.indexOf(seg.complexSeparator());
             if (complexSeparatorIndex >= 0) {
-              modifableInput.add(inPos, next.substring(complexSeparatorIndex + 1));
+              modifiableInput.add(inPos, next.substring(complexSeparatorIndex + 1));
               next = next.substring(0, complexSeparatorIndex);
-              modifableInput.set(inPos - 1, next);
+              modifiableInput.set(inPos - 1, next);
             } else {
-              // No coplex resource ID separator found in the literal when we expected one.
+              // No complex resource ID separator found in the literal when we expected one.
               return false;
             }
           }
@@ -635,7 +665,7 @@ private boolean match(
                 segsToMatch++;
             }
           }
-          int available = (modifableInput.size() - inPos) - segsToMatch;
+          int available = (modifiableInput.size() - inPos) - segsToMatch;
           // If this segment is empty, make sure it is still captured.
           if (available == 0 && !values.containsKey(currentVar)) {
             values.put(currentVar, "");
@@ -643,11 +673,11 @@ private boolean match(
           while (available-- > 0) {
             values.put(
                 currentVar,
-                concatCaptures(values.get(currentVar), decodeUrl(modifableInput.get(inPos++))));
+                concatCaptures(values.get(currentVar), decodeUrl(modifiableInput.get(inPos++))));
           }
       }
     }
-    return inPos == modifableInput.size();
+    return inPos == modifiableInput.size();
   }
 
   private static String concatCaptures(@Nullable String cur, String next) {
@@ -661,9 +691,10 @@ private static String concatCaptures(@Nullable String cur, String next) {
    * Instantiate the template based on the given variable assignment. Performs proper URL escaping
    * of variable assignments.
    *
-   * 

Note that free wildcards in the template must have bindings of '$n' variables, where 'n' is - * the position of the wildcard (starting at 0). See the documentation of {@link #match(String)} - * for details. + *

+ * Note that free wildcards in the template must have bindings of '$n' variables, where 'n' is the + * position of the wildcard (starting at 0). See the documentation of {@link #match(String)} for + * details. * * @throws ValidationException if a variable occurs in the template without a binding. */ @@ -838,14 +869,15 @@ private static ImmutableList parseTemplate(String template) { int pathWildCardBound = 0; for (String seg : Splitter.on('/').trimResults().split(template)) { - if (DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() - || DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find()) { + if (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() + || COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find()) { throw new ValidationException("parse error: invalid begin or end character in '%s'", seg); } // Disallow zero or multiple delimiters between variable names. - if (Pattern.compile("\\}(_|\\-|\\.|~){2,}\\{").matcher(seg).find()) { + if (MULTIPLE_COMPLEX_DELIMITER_PATTERN.matcher(seg).find() + || MISSING_COMPLEX_DELIMITER_PATTERN.matcher(seg).find()) { throw new ValidationException( - "parse error: two consecutive delimiter characters in '%s'", seg); + "parse error: missing or 2+ consecutive delimiter characters in '%s'", seg); } // If segment starts with '{', a binding group starts. boolean bindingStarts = seg.startsWith("{"); @@ -858,19 +890,16 @@ private static ImmutableList parseTemplate(String template) { seg = seg.substring(1); // Check for invalid complex resource ID delimiters. - if (Pattern.compile("\\}[^_\\-\\.~]\\{").matcher(seg).find() - || Pattern.compile("\\}\\{").matcher(seg).find()) { + if (INVALID_COMPLEX_DELIMITER_PATTERN.matcher(seg).find()) { throw new ValidationException( - "parse error: missing or invalid complex resource ID delimiter character in '%s'", - seg); + "parse error: invalid complex resource ID delimiter character in '%s'", seg); } - Matcher complexPatternDelimiterMatcher = - Pattern.compile("\\}(_|\\-|\\.|~){1}").matcher(seg); + Matcher complexPatternDelimiterMatcher = END_SEGMENT_COMPLEX_DELIMITER_PATTERN.matcher(seg); complexDelimiterFound = complexPatternDelimiterMatcher.find(); // Look for complex resource names. - // Need to handles something like "{user_a}~{user_b}". + // Need to handle something like "{user_a}~{user_b}". if (complexDelimiterFound) { builder.addAll(parseComplexResourceId(seg)); } else { @@ -965,7 +994,7 @@ private static List parseComplexResourceId(String seg) { List segments = new ArrayList<>(); List separatorIndices = new ArrayList<>(); - Matcher complexPatternDelimiterMatcher = Pattern.compile("\\}(_|\\-|\\.|~){1}").matcher(seg); + Matcher complexPatternDelimiterMatcher = END_SEGMENT_COMPLEX_DELIMITER_PATTERN.matcher(seg); boolean delimiterFound = complexPatternDelimiterMatcher.find(); while (delimiterFound) { @@ -974,7 +1003,7 @@ private static List parseComplexResourceId(String seg) { delimiterIndex += 1; } String currDelimiter = seg.substring(delimiterIndex, delimiterIndex + 1); - if (!DELIMITER_PATTERN.matcher(currDelimiter).find()) { + if (!COMPLEX_DELIMITER_PATTERN.matcher(currDelimiter).find()) { throw new ValidationException( "parse error: invalid complex ID delimiter '%s' in '%s'", currDelimiter, seg); } diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 338ed3643..33b9033b8 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -262,13 +262,13 @@ public void complexResourceIdNoSeparator() { PathTemplate.create("projects/{project}/zones/{zone_a}{zone_b}"); thrown.expectMessage( String.format( - "parse error: missing or invalid complex resource ID delimiter character in '%s'", + "parse error: missing or 2+ consecutive delimiter characters in '%s'", "{zone_a}{zone_b}")); PathTemplate.create("projects/{project}/zones/{zone_a}_{zone_b}{zone_c}"); thrown.expectMessage( String.format( - "parse error: missing or invalid complex resource ID delimiter character in '%s'", + "parse error: missing or 2+ consecutive delimiter characters in '%s'", "{zone_a}_{zone_b}{zone_c}")); } @@ -283,7 +283,7 @@ public void complexResourceIdInvalidDelimiter() { String.format("projects/{project=*}/zones/{zone_a}%s{zone_b}", invalidDelimiter)); thrown.expectMessage( String.format( - "parse error: missing or invalid complex resource ID delimiter character in '%s'", + "parse error: invalid complex resource ID delimiter character in '%s'", String.format("{zone_a}%s{zone_b}", invalidDelimiter))); } } @@ -398,12 +398,13 @@ public void complexResourceMultipleDelimiters() { PathTemplate.create("projects/*/zones/{zone_a}~.{zone_b}"); thrown.expectMessage( String.format( - "parse error: two consecutive delimiter characters in '%s'", "{zone_a}~.{zone_b}")); + "parse error: missing or 2+ consecutive delimiter characters in '%s'", + "{zone_a}~.{zone_b}")); PathTemplate.create("projects/*/zones/{zone_a}~{zone_b}..{zone_c}"); thrown.expectMessage( String.format( - "parse error: two consecutive delimiter characters in '%s'", + "parse error: missing or 2+ consecutive delimiter characters in '%s'", "{zone_a}~{zone_b}..{zone_c}")); String pathString = "projects/project_123/zones/lorum~ipsum"; From 3bfdecbfffdc932822194398369eca5473f47af5 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Mon, 18 May 2020 13:15:51 -0400 Subject: [PATCH 60/66] -a (#138) update guava --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b4e410765..0c3f53571 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ ext { javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', auto_value_annotations: 'com.google.auto.value:auto-value-annotations:1.7', auto_value: 'com.google.auto.value:auto-value:1.7', - guava: 'com.google.guava:guava:28.2-android', + guava: 'com.google.guava:guava:29.0-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.4', From 3a1629e6c9ee11fd2433435a069617b1ee1c1cd5 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 18 May 2020 19:20:23 +0200 Subject: [PATCH 61/66] chore(deps): update dependency com.google.auto.value:auto-value-annotations to v1.7.2 (#136) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0c3f53571..14d4eb872 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ ext { // Shortcuts for libraries we are using libraries = [ javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', - auto_value_annotations: 'com.google.auto.value:auto-value-annotations:1.7', + auto_value_annotations: 'com.google.auto.value:auto-value-annotations:1.7.2', auto_value: 'com.google.auto.value:auto-value:1.7', guava: 'com.google.guava:guava:29.0-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', From 162c04f1dd966edb4a01ce14586b68108c6dd597 Mon Sep 17 00:00:00 2001 From: Elliotte Rusty Harold Date: Mon, 18 May 2020 13:26:45 -0400 Subject: [PATCH 62/66] deps: prevent renovate-bot from updating to guava -jre versions (#142) fixes #140 --- renovate.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index f45d8f110..b76243f10 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,21 @@ { "extends": [ - "config:base" + ":separateMajorReleases", + ":combinePatchMinorReleases", + ":ignoreUnstable", + ":prImmediately", + ":updateNotScheduled", + ":automergeDisabled", + ":ignoreModulesAndTests", + ":maintainLockFilesDisabled", + ":autodetectPinVersions" + ], + "packageRules": [ + { + "packagePatterns": [ + "^com.google.guava:" + ], + "versionScheme": "docker" + } ] } From 63430bda37747d740ee77db796e8f526153f307d Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Mon, 18 May 2020 22:24:29 +0200 Subject: [PATCH 63/66] chore(deps): update dependency com.google.auto.value:auto-value to v1.7.2 (#135) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 14d4eb872..859349425 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ ext { libraries = [ javax_annotations: 'javax.annotation:javax.annotation-api:1.3.2', auto_value_annotations: 'com.google.auto.value:auto-value-annotations:1.7.2', - auto_value: 'com.google.auto.value:auto-value:1.7', + auto_value: 'com.google.auto.value:auto-value:1.7.2', guava: 'com.google.guava:guava:29.0-android', jsr305: 'com.google.code.findbugs:jsr305:3.0.2', error_prone_annotations: 'com.google.errorprone:error_prone_annotations:2.3.4', From fe80f811acf0d6d904af20941401eaebb6e90563 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Tue, 26 May 2020 10:27:19 -0700 Subject: [PATCH 64/66] chore: release v1.9.1 (#144) --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index f8e233b27..9ab8337f3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.0 +1.9.1 From 2eb053419fcc54df8459a25b6ba2732a29aab698 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 27 May 2020 18:30:16 +0300 Subject: [PATCH 65/66] fix: update mockito dependency (#146) fix: update mockito dependency --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 859349425..4d5288a54 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,6 @@ ext { // Testing junit: 'junit:junit:4.13', - mockito: 'org.mockito:mockito-core:1.10.19', truth: 'com.google.truth:truth:1.0.1', ] } @@ -69,7 +68,6 @@ dependencies { compileOnly libraries.error_prone_annotations testCompile libraries.junit, - libraries.mockito, libraries.truth } From 007782eb345af6fa4ae49bfcd9702cf4f5d5adf4 Mon Sep 17 00:00:00 2001 From: Mira Leung Date: Wed, 3 Jun 2020 22:38:21 +0000 Subject: [PATCH 66/66] fix: parse collection wildcards and _deleted-topic_ patterns --- .../google/api/pathtemplate/PathTemplate.java | 21 +++++++++++++--- .../api/pathtemplate/PathTemplateTest.java | 25 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 970fa510e..45071968c 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -869,8 +869,17 @@ private static ImmutableList parseTemplate(String template) { int pathWildCardBound = 0; for (String seg : Splitter.on('/').trimResults().split(template)) { - if (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() - || COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find()) { + // Handle _deleted-topic_ for PubSub. + if (seg.equals("_deleted-topic_")) { + builder.add(Segment.create(SegmentKind.LITERAL, seg)); + continue; + } + + boolean isLastSegment = (template.indexOf(seg) + seg.length()) == template.length(); + boolean isCollectionWildcard = !isLastSegment && (seg.equals("-") || seg.equals("-}")); + if (!isCollectionWildcard + && (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() + || COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find())) { throw new ValidationException("parse error: invalid begin or end character in '%s'", seg); } // Disallow zero or multiple delimiters between variable names. @@ -896,7 +905,7 @@ private static ImmutableList parseTemplate(String template) { } Matcher complexPatternDelimiterMatcher = END_SEGMENT_COMPLEX_DELIMITER_PATTERN.matcher(seg); - complexDelimiterFound = complexPatternDelimiterMatcher.find(); + complexDelimiterFound = !isCollectionWildcard && complexPatternDelimiterMatcher.find(); // Look for complex resource names. // Need to handle something like "{user_a}~{user_b}". @@ -915,6 +924,8 @@ private static ImmutableList parseTemplate(String template) { throw new ValidationException( "parse error: invalid binding syntax in '%s'", template); } + } else if (seg.indexOf('-') <= 0 && isCollectionWildcard) { + implicitWildcard = true; } else { // Looking at something like "{name=wildcard}". varName = seg.substring(0, i).trim(); @@ -957,6 +968,10 @@ private static ImmutableList parseTemplate(String template) { } // If the wildcard is implicit, seg will be empty. Just continue. break; + case "-": + builder.add(Segment.WILDCARD); + implicitWildcard = false; + break; default: builder.add(Segment.create(SegmentKind.LITERAL, seg)); } diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 33b9033b8..ccac31f6d 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -317,6 +317,31 @@ public void complexResourceIdMixedSeparators() { Truth.assertThat(match.get("zone_d")).isEqualTo("europe-west2-b"); } + @Test + public void collectionWildcardMatchingInParent() { + PathTemplate template = PathTemplate.create("v1/publishers/-/books/{book}"); + Map match = + template.match( + "https://example.googleapis.com/v1/publishers/publisher-abc/books/blockchain_for_babies"); + Truth.assertThat(match).isNotNull(); + + template = PathTemplate.create("/v1/{parent=rooms/-}/blurbs/{blurb}"); + match = template.match("https://example.googleapis.com/v1/rooms/den/blurbs/asdf"); + Truth.assertThat(match).isNotNull(); + } + + @Test + public void collectionWildcardMatchingInvalid() { + thrown.expect(ValidationException.class); + PathTemplate.create("v1/publishers/{publisher}/books/-"); + } + + @Test + public void complexResourceIdPubSubDeletedTopic() { + PathTemplate template = PathTemplate.create("_deleted-topic_"); + Truth.assertThat(template).isNotNull(); + } + @Test public void complexResourceIdInParent() { // One parent has a complex resource ID.