001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014-2015 ForgeRock AS.
015 */
016
017package org.forgerock.http;
018
019import static java.util.Collections.*;
020import static org.forgerock.http.util.Paths.joinPath;
021import static org.forgerock.http.util.Uris.*;
022
023import java.net.URI;
024import java.net.URISyntaxException;
025import java.util.ArrayList;
026import java.util.List;
027
028import org.forgerock.http.util.Paths;
029
030/**
031 * A MutableUri is a modifiable {@link URI} substitute.
032 * Unlike URIs, which are immutable, a MutableUri can have its fields updated independently.
033 * That makes it easier if you just want to change a element of an Uri.
034 *
035 * @see URI
036 */
037public final class MutableUri implements Comparable<MutableUri> {
038
039
040    /**
041     * Factory method for avoiding typing {@code new MutableUri("http://...")}.
042     * @param uri URL encoded URI
043     * @return a new MutableUri instance
044     * @throws URISyntaxException if the given Uri is not well-formed
045     */
046    public static MutableUri uri(String uri) throws URISyntaxException {
047        return new MutableUri(uri);
048    }
049
050    /**
051     * The real URI, hidden by this class. Recreated each time a field is updated.
052     */
053    private URI uri;
054    private List<String> pathElements;
055
056    /**
057     * Builds a new MutableUri using the given URI.
058     * @param uri URI
059     */
060    public MutableUri(final URI uri) {
061        this.uri = uri;
062        setPathElements(uri.getRawPath());
063    }
064
065    /**
066     * Builds a new MutableUri with deep copy.
067     * @param mutableUri URI
068     */
069    public MutableUri(final MutableUri mutableUri) {
070        this(mutableUri.asURI());
071    }
072
073    /**
074     * Builds a new MutableUri using the given URL encoded String URI.
075     * @param uri URL encoded URI
076     * @throws URISyntaxException if the given Uri is not well-formed
077     */
078    public MutableUri(final String uri) throws URISyntaxException {
079        this(new URI(uri));
080    }
081
082    /**
083     * Builds a new MutableUri using the given fields values (decoded values).
084     * @param scheme Scheme name
085     * @param userInfo User name and authorization information
086     * @param host Host name
087     * @param port Port number
088     * @param path Path
089     * @param query Query
090     * @param fragment Fragment
091     * @throws URISyntaxException if the produced URI is not well-formed
092     */
093    public MutableUri(String scheme,
094                      String userInfo,
095                      String host,
096                      int port,
097                      String path,
098                      String query,
099                      String fragment)
100            throws URISyntaxException {
101        this(new URI(scheme, userInfo, host, port, path, query, fragment));
102    }
103
104    /**
105     * Returns the equivalent {@link URI} instance.
106     * @return the equivalent {@link URI} instance.
107     */
108    public URI asURI() {
109        return uri;
110    }
111
112    /**
113     * Returns the scheme name.
114     * @return the scheme name.
115     */
116    public String getScheme() {
117        return uri.getScheme();
118    }
119
120    /**
121     * Update the scheme of this MutableUri.
122     * @param scheme new scheme name
123     * @throws URISyntaxException if the new equivalent URI is invalid
124     */
125    public void setScheme(final String scheme) throws URISyntaxException {
126        this.uri = create(scheme,
127                          uri.getRawUserInfo(),
128                          uri.getHost(),
129                          uri.getPort(),
130                          uri.getRawPath(),
131                          uri.getRawQuery(),
132                          uri.getRawFragment());
133    }
134
135    /**
136     * Returns the user info element.
137     * @return the user info element.
138     */
139    public String getUserInfo() {
140        return uri.getUserInfo();
141    }
142
143    /**
144     * Returns the raw (encoded) user info element.
145     * @return the raw user info element.
146     */
147    public String getRawUserInfo() {
148        return uri.getRawUserInfo();
149    }
150
151    /**
152     * Update the user info (not encoded) of this MutableUri.
153     * @param userInfo new user info element (not encoded)
154     * @throws URISyntaxException if the new equivalent URI is invalid
155     */
156    public void setUserInfo(final String userInfo) throws URISyntaxException {
157        URI other = new URI(null, userInfo, "ignored", -1, null, null, null);
158        setRawUserInfo(other.getRawUserInfo());
159    }
160
161    /**
162     * Update the user info (encoded) of this MutableUri.
163     * @param rawUserInfo new user info element (encoded)
164     * @throws URISyntaxException if the new equivalent URI is invalid
165     */
166    public void setRawUserInfo(String rawUserInfo) throws URISyntaxException {
167        uri = create(uri.getScheme(),
168                     rawUserInfo,
169                     uri.getHost(),
170                     uri.getPort(),
171                     uri.getRawPath(),
172                     uri.getRawQuery(),
173                     uri.getRawFragment());
174    }
175
176    /**
177     * Returns the host element.
178     * @return the host element.
179     */
180    public String getHost() {
181        return uri.getHost();
182    }
183
184    /**
185     * Update the host name of this MutableUri.
186     * @param host new host element
187     * @throws URISyntaxException if the new equivalent URI is invalid
188     */
189    public void setHost(final String host) throws URISyntaxException {
190        this.uri = create(uri.getScheme(),
191                          uri.getRawUserInfo(),
192                          host,
193                          uri.getPort(),
194                          uri.getRawPath(),
195                          uri.getRawQuery(),
196                          uri.getRawFragment());
197    }
198
199    /**
200     * Returns the port element.
201     * @return the port element.
202     */
203    public int getPort() {
204        return uri.getPort();
205    }
206
207    /**
208     * Update the port of this MutableUri.
209     * @param port new port number
210     * @throws URISyntaxException if the new equivalent URI is invalid
211     */
212    public void setPort(final int port) throws URISyntaxException {
213        uri = create(uri.getScheme(),
214                     uri.getRawUserInfo(),
215                     uri.getHost(),
216                     port,
217                     uri.getRawPath(),
218                     uri.getRawQuery(),
219                     uri.getRawFragment());
220    }
221
222    /**
223     * Returns the path element.
224     * @return the path element.
225     */
226    public String getPath() {
227        return uri.getPath();
228    }
229
230    /**
231     * Returns the raw (encoded) path element.
232     * @return the raw path element.
233     */
234    public String getRawPath() {
235        return uri.getRawPath();
236    }
237
238    /**
239     * Update the path (not encoded) of this MutableUri.
240     * @param path new path element (not encoded)
241     * @throws URISyntaxException if the new equivalent URI is invalid
242     */
243    public void setPath(final String path) throws URISyntaxException {
244        URI other = new URI(null, null, "ignored", -1, path, null, null);
245        setRawPath(other.getRawPath());
246    }
247
248    /**
249     * Update the pah (encoded) of this MutableUri.
250     * @param rawPath new path element (encoded)
251     * @throws URISyntaxException if the new equivalent URI is invalid
252     */
253    public void setRawPath(String rawPath) throws URISyntaxException {
254        uri = create(uri.getScheme(),
255                     uri.getRawUserInfo(),
256                     uri.getHost(),
257                     uri.getPort(),
258                     rawPath,
259                     uri.getRawQuery(),
260                     uri.getRawFragment());
261        setPathElements(uri.getRawPath());
262    }
263
264    /**
265     * Returns the path element.
266     * @return the path element.
267     */
268    public String getQuery() {
269        return uri.getQuery();
270    }
271
272    /**
273     * Returns the raw (encoded) query element.
274     * @return the raw query element.
275     */
276    public String getRawQuery() {
277        return uri.getRawQuery();
278    }
279
280    /**
281     * Update the query string (not encoded) of this MutableUri.
282     * @param query new query string element (not encoded)
283     * @throws URISyntaxException if the new equivalent URI is invalid
284     */
285    public void setQuery(final String query) throws URISyntaxException {
286        URI other = new URI(null, null, "ignored", -1, null, query, null);
287        setRawQuery(other.getRawQuery());
288    }
289
290    /**
291     * Update the query (encoded) of this MutableUri.
292     * @param rawQuery new query element (encoded)
293     * @throws URISyntaxException if the new equivalent URI is invalid
294     */
295    public void setRawQuery(String rawQuery) throws URISyntaxException {
296        uri = create(uri.getScheme(),
297                     uri.getRawUserInfo(),
298                     uri.getHost(),
299                     uri.getPort(),
300                     uri.getRawPath(),
301                     rawQuery,
302                     uri.getRawFragment());
303    }
304
305    /**
306     * Returns the fragment element.
307     * @return the fragment element.
308     */
309    public String getFragment() {
310        return uri.getFragment();
311    }
312
313    /**
314     * Returns the raw (encoded) fragment element.
315     * @return the raw fragment element.
316     */
317    public String getRawFragment() {
318        return uri.getRawFragment();
319    }
320
321    /**
322     * Update the fragment (not encoded) of this MutableUri.
323     * @param fragment new fragment element (not encoded)
324     * @throws URISyntaxException if the new equivalent URI is invalid
325     */
326    public void setFragment(final String fragment) throws URISyntaxException {
327        URI other = new URI(null, null, "ignored", -1, null, null, fragment);
328        setRawFragment(other.getRawFragment());
329    }
330
331    /**
332     * Update the fragment (encoded) of this MutableUri.
333     * @param rawFragment new framgent element (encoded)
334     * @throws URISyntaxException if the new equivalent URI is invalid
335     */
336    public void setRawFragment(String rawFragment) throws URISyntaxException {
337        uri = create(uri.getScheme(),
338                     uri.getRawUserInfo(),
339                     uri.getHost(),
340                     uri.getPort(),
341                     uri.getRawPath(),
342                     uri.getRawQuery(),
343                     rawFragment);
344    }
345
346    /**
347     * Returns the authority compound element.
348     * @return the authority compound element.
349     */
350    public String getAuthority() {
351        return uri.getAuthority();
352    }
353
354    /**
355     * Returns the raw (encoded) authority compound element.
356     * @return the authority compound element.
357     */
358    public String getRawAuthority() {
359        return uri.getRawAuthority();
360    }
361
362    /**
363     * Sets the {@literal pathElements} from the URI path (encoded value).
364     * <p>
365     * This method does not set or recreate the {@literal uri}, this is the
366     * responsibility of the method caller.
367     *
368     * @param rawPath The raw (URI-encoded) path.
369     */
370    private void setPathElements(final String rawPath) {
371        this.pathElements = new ArrayList<String>(Paths.getPathElements(rawPath)) {
372            @Override
373            public String toString() {
374                return joinPath(this);
375            }
376        };
377    }
378
379    /**
380     * Return the URI path elements as an immutable {@code List}. The {@code toString} method of the
381     * returned object will return the URL-encoded path elements joined with {@code "/"}.
382     *
383     * @return The URI path elements as an immutable {@code List}.
384     */
385    public List<String> getPathElements() {
386        return unmodifiableList(pathElements);
387    }
388
389    /**
390     * Changes the base scheme, host and port of this MutableUri to that specified in a base URI,
391     * or leaves them unchanged if the base URI is {@code null}. This implementation only
392     * uses scheme, host and port. The remaining components of the URI remain intact.
393     *
394     * @param base the URI to base the other URI on.
395     * @return this (rebased) instance
396     */
397    public MutableUri rebase(MutableUri base) {
398        if (base == null) {
399            return this;
400        }
401        String scheme = base.getScheme();
402        String host = base.getHost();
403        int port = base.getPort();
404        if (scheme == null || host == null) {
405            return this;
406        }
407        try {
408            setScheme(scheme);
409            setHost(host);
410            setPort(port);
411        } catch (URISyntaxException e) {
412            throw new IllegalStateException(e);
413        }
414        return this;
415    }
416
417    /**
418     * Changes the base scheme, host and port of this MutableUri to that specified in a base URI,
419     * or leaves them unchanged if the base URI is {@code null}. This implementation only
420     * uses scheme, host and port. The remaining components of the URI remain intact.
421     *
422     * @param base the URI to base the other URI on.
423     * @return this (rebased) instance
424     */
425    public MutableUri rebase(URI base) {
426        return rebase(new MutableUri(base));
427    }
428
429    @Override
430    public int compareTo(final MutableUri o) {
431        return asURI().compareTo(o.asURI());
432    }
433
434    /**
435     * Relativizes the given URI against this URI.
436     * @param uri the uri to relativizes against this instance
437     * @return this instance (mutated)
438     * @see URI#relativize(URI)
439     */
440    public MutableUri relativize(final MutableUri uri) {
441        this.uri = this.uri.relativize(uri.asURI());
442        setPathElements(this.uri.getRawPath());
443        return this;
444    }
445
446    /**
447     * Resolves the given URI against this URI.
448     * @param uri the uri to resolve against this instance
449     * @return this instance (mutated)
450     * @see URI#resolve(URI)
451     */
452    public MutableUri resolve(final MutableUri uri) {
453        this.uri = this.uri.resolve(uri.asURI());
454        setPathElements(this.uri.getRawPath());
455        return this;
456    }
457
458    @Override
459    public String toString() {
460        return uri.toString();
461    }
462
463    /**
464     * Returns the content of this URI as a US-ASCII string.
465     * @return the content of this URI as a US-ASCII string.
466     */
467    public String toASCIIString() {
468        return uri.toASCIIString();
469    }
470
471    @Override
472    public boolean equals(final Object o) {
473        if (this == o) {
474            return true;
475        }
476        if (o == null || !(o instanceof MutableUri)) {
477            return false;
478        }
479        MutableUri that = (MutableUri) o;
480        return uri.equals(that.uri);
481
482    }
483
484    @Override
485    public int hashCode() {
486        return uri.hashCode();
487    }
488}