• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016, Google Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 package com.google.api.pathtemplate;
32 
33 import com.google.common.base.Preconditions;
34 import com.google.common.collect.ImmutableMap;
35 import com.google.common.collect.Sets;
36 import java.util.Collection;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.Set;
40 import javax.annotation.Nullable;
41 
42 /**
43  * Class for representing and working with resource names.
44  *
45  * <p>A resource name is represented by {@link PathTemplate}, an assignment to variables in the
46  * template, and an optional endpoint. The {@code ResourceName} class implements the map interface
47  * (unmodifiable) to work with the variable assignments, and has methods to reproduce the string
48  * representation of the name, to construct new names, and to dereference names into resources.
49  *
50  * <p>As a resource name essentially represents a match of a path template against a string, it can
51  * be also used for other purposes than naming resources. However, not all provided methods may make
52  * sense in all applications.
53  *
54  * <p>Usage examples:
55  *
56  * <pre>{@code
57  * PathTemplate template = PathTemplate.create("shelves/*&#47;books/*");
58  * TemplatedResourceName resourceName = TemplatedResourceName.create(template, "shelves/s1/books/b1");
59  * assert resourceName.get("$1").equals("b1");
60  * assert resourceName.parentName().toString().equals("shelves/s1/books");
61  * }</pre>
62  */
63 public class TemplatedResourceName implements Map<String, String> {
64 
65   // ResourceName Resolver
66   // =====================
67 
68   /** Represents a resource name resolver which can be registered with this class. */
69   public interface Resolver {
70     /** Resolves the resource name into a resource by calling the underlying API. */
resolve(Class<T> resourceType, TemplatedResourceName name, @Nullable String version)71     <T> T resolve(Class<T> resourceType, TemplatedResourceName name, @Nullable String version);
72   }
73 
74   // The registered resource name resolver.
75   // TODO(wrwg): its a bit spooky to have this static global. Think of ways to
76   // configure this from the outside instead if programmatically (e.g. java properties).
77   private static volatile Resolver resourceNameResolver =
78       new Resolver() {
79         @Override
80         public <T> T resolve(Class<T> resourceType, TemplatedResourceName name, String version) {
81           throw new IllegalStateException(
82               "No resource name resolver is registered in ResourceName class.");
83         }
84       };
85 
86   /**
87    * Sets the resource name resolver which is used by the {@link #resolve(Class, String)} method. By
88    * default, no resolver is registered.
89    */
registerResourceNameResolver(Resolver resolver)90   public static void registerResourceNameResolver(Resolver resolver) {
91     resourceNameResolver = resolver;
92   }
93 
94   // ResourceName
95   // ============
96 
97   /**
98    * Creates a new resource name based on given template and path. The path must match the template,
99    * otherwise null is returned.
100    *
101    * @throws ValidationException if the path does not match the template.
102    */
create(PathTemplate template, String path)103   public static TemplatedResourceName create(PathTemplate template, String path) {
104     Map<String, String> values = template.match(path);
105     if (values == null) {
106       throw new ValidationException("path '%s' does not match template '%s'", path, template);
107     }
108     return new TemplatedResourceName(template, values, null);
109   }
110 
111   /**
112    * Creates a new resource name from a template and a value assignment for variables.
113    *
114    * @throws ValidationException if not all variables in the template are bound.
115    */
create(PathTemplate template, Map<String, String> values)116   public static TemplatedResourceName create(PathTemplate template, Map<String, String> values) {
117     if (!values.keySet().containsAll(template.vars())) {
118       Set<String> unbound = Sets.newLinkedHashSet(template.vars());
119       unbound.removeAll(values.keySet());
120       throw new ValidationException("unbound variables: %s", unbound);
121     }
122     return new TemplatedResourceName(template, values, null);
123   }
124 
125   /**
126    * Creates a new resource name based on given template and path, where the path contains an
127    * endpoint. If the path does not match, null is returned.
128    */
129   @Nullable
createFromFullName(PathTemplate template, String path)130   public static TemplatedResourceName createFromFullName(PathTemplate template, String path) {
131     Map<String, String> values = template.matchFromFullName(path);
132     if (values == null) {
133       return null;
134     }
135     return new TemplatedResourceName(template, values, null);
136   }
137 
138   private final PathTemplate template;
139   private final ImmutableMap<String, String> values;
140   private final String endpoint;
141 
142   private volatile String stringRepr;
143 
TemplatedResourceName( PathTemplate template, Map<String, String> values, String endpoint)144   private TemplatedResourceName(
145       PathTemplate template, Map<String, String> values, String endpoint) {
146     this.template = template;
147     this.values = ImmutableMap.copyOf(values);
148     this.endpoint = endpoint;
149   }
150 
151   @Override
toString()152   public String toString() {
153     if (stringRepr == null) {
154       stringRepr = template.instantiate(values);
155     }
156     return stringRepr;
157   }
158 
159   @Override
equals(Object obj)160   public boolean equals(Object obj) {
161     if (!(obj instanceof TemplatedResourceName)) {
162       return false;
163     }
164     TemplatedResourceName other = (TemplatedResourceName) obj;
165     return Objects.equals(template, other.template)
166         && Objects.equals(endpoint, other.endpoint)
167         && Objects.equals(values, other.values);
168   }
169 
170   @Override
hashCode()171   public int hashCode() {
172     return Objects.hash(template, endpoint, values);
173   }
174 
175   /** Gets the template associated with this resource name. */
template()176   public PathTemplate template() {
177     return template;
178   }
179 
180   /** Checks whether the resource name has an endpoint. */
hasEndpoint()181   public boolean hasEndpoint() {
182     return endpoint != null;
183   }
184 
185   /** Returns the endpoint of this resource name, or null if none is defined. */
186   @Nullable
endpoint()187   public String endpoint() {
188     return endpoint;
189   }
190 
191   /** Returns a resource name with specified endpoint. */
withEndpoint(String endpoint)192   public TemplatedResourceName withEndpoint(String endpoint) {
193     return new TemplatedResourceName(template, values, Preconditions.checkNotNull(endpoint));
194   }
195 
196   /**
197    * Returns the parent resource name. For example, if the name is {@code shelves/s1/books/b1}, the
198    * parent is {@code shelves/s1/books}.
199    */
parentName()200   public TemplatedResourceName parentName() {
201     PathTemplate parentTemplate = template.parentTemplate();
202     return new TemplatedResourceName(parentTemplate, values, endpoint);
203   }
204 
205   /**
206    * Returns true of the resource name starts with the parent resource name, i.e. is a child of the
207    * parent.
208    */
startsWith(TemplatedResourceName parentName)209   public boolean startsWith(TemplatedResourceName parentName) {
210     // TODO: more efficient implementation.
211     return toString().startsWith(parentName.toString());
212   }
213 
214   /**
215    * Attempts to resolve a resource name into a resource, by calling the associated API. The
216    * resource name must have an endpoint. An optional version can be specified to determine in which
217    * version of the API to call.
218    */
resolve(Class<T> resourceType, @Nullable String version)219   public <T> T resolve(Class<T> resourceType, @Nullable String version) {
220     Preconditions.checkArgument(hasEndpoint(), "Resource name must have an endpoint.");
221     return resourceNameResolver.resolve(resourceType, this, version);
222   }
223 
224   // Map Interface
225   // =============
226 
227   @Override
size()228   public int size() {
229     return values.size();
230   }
231 
232   @Override
isEmpty()233   public boolean isEmpty() {
234     return values.isEmpty();
235   }
236 
237   @Override
containsKey(Object key)238   public boolean containsKey(Object key) {
239     return values.containsKey(key);
240   }
241 
242   @Override
containsValue(Object value)243   public boolean containsValue(Object value) {
244     return values.containsValue(value);
245   }
246 
247   @Override
get(Object key)248   public String get(Object key) {
249     return values.get(key);
250   }
251 
252   @Override
253   @Deprecated
put(String key, String value)254   public String put(String key, String value) {
255     return values.put(key, value);
256   }
257 
258   @Override
259   @Deprecated
remove(Object key)260   public String remove(Object key) {
261     return values.remove(key);
262   }
263 
264   @Override
265   @Deprecated
putAll(Map<? extends String, ? extends String> m)266   public void putAll(Map<? extends String, ? extends String> m) {
267     values.putAll(m);
268   }
269 
270   @Override
271   @Deprecated
clear()272   public void clear() {
273     values.clear();
274   }
275 
276   @Override
keySet()277   public Set<String> keySet() {
278     return values.keySet();
279   }
280 
281   @Override
values()282   public Collection<String> values() {
283     return values.values();
284   }
285 
286   @Override
entrySet()287   public Set<Entry<String, String>> entrySet() {
288     return values.entrySet();
289   }
290 }
291