brooklyn.util.net.Urls.java Source code

Java tutorial

Introduction

Here is the source code for brooklyn.util.net.Urls.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package brooklyn.util.net;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.annotation.Nullable;

import brooklyn.util.text.Strings;

import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.io.BaseEncoding;
import com.google.common.net.MediaType;

public class Urls {

    public static Function<String, URI> stringToUriFunction() {
        return StringToUri.INSTANCE;
    }

    public static Function<String, URL> stringToUrlFunction() {
        return StringToUrl.INSTANCE;
    }

    private static enum StringToUri implements Function<String, URI> {
        INSTANCE;
        @Override
        public URI apply(@Nullable String input) {
            return toUri(input);
        }

        @Override
        public String toString() {
            return "StringToUri";
        }
    }

    private static enum StringToUrl implements Function<String, URL> {
        INSTANCE;
        @Override
        public URL apply(@Nullable String input) {
            return toUrl(input);
        }

        @Override
        public String toString() {
            return "StringToUrl";
        }
    }

    /** creates a URL, preserving null and propagating exceptions *unchecked* */
    public static final URL toUrl(@Nullable String url) {
        if (url == null)
            return null;
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            // FOAD
            throw Throwables.propagate(e);
        }
    }

    /** creates a URL, preserving null and propagating exceptions *unchecked* */
    public static final URL toUrl(@Nullable URI uri) {
        if (uri == null)
            return null;
        try {
            return uri.toURL();
        } catch (MalformedURLException e) {
            // FOAD
            throw Throwables.propagate(e);
        }
    }

    /** creates a URI, preserving null and propagating exceptions *unchecked* */
    public static final URI toUri(@Nullable String uri) {
        if (uri == null)
            return null;
        return URI.create(uri);
    }

    /** creates a URI, preserving null and propagating exceptions *unchecked* */
    public static final URI toUri(@Nullable URL url) {
        if (url == null)
            return null;
        try {
            return url.toURI();
        } catch (URISyntaxException e) {
            // FOAD
            throw Throwables.propagate(e);
        }
    }

    /** returns true if the string begins with a non-empty string of letters followed by a colon,
     * i.e. "protocol:" returns true, but "/" returns false */
    public static boolean isUrlWithProtocol(String x) {
        if (x == null)
            return false;
        for (int i = 0; i < x.length(); i++) {
            char c = x.charAt(i);
            if (c == ':')
                return i > 0;
            if (!Character.isLetter(c))
                return false;
        }
        return false;
    }

    /** returns the items with exactly one "/" between items (whether or not the individual items start or end with /),
     * except where character before the / is a : (url syntax) in which case it will permit multiple (will not remove any).
     * Throws a NullPointerException if any elements of 'items' is null.
     *  */
    public static String mergePaths(String... items) {
        List<String> parts = Arrays.asList(items);

        if (parts.contains(null)) {
            throw new NullPointerException(String
                    .format("Unable to reliably merge path from parts: %s; input contains null values", parts));
        }

        StringBuilder result = new StringBuilder();
        for (String part : parts) {
            boolean trimThisMerge = result.length() > 0 && !result.toString().endsWith("://")
                    && !result.toString().endsWith(":///") && !result.toString().endsWith(":");
            if (trimThisMerge) {
                while (result.length() > 0 && result.charAt(result.length() - 1) == '/')
                    result.deleteCharAt(result.length() - 1);
                result.append('/');
            }
            int i = result.length();
            result.append(part);
            if (trimThisMerge) {
                while (result.length() > i && result.charAt(i) == '/')
                    result.deleteCharAt(i);
            }
        }
        return result.toString();
    }

    /** encodes the string suitable for use in a URL, using default character set
     * (non-deprecated version of URLEncoder.encode) */
    @SuppressWarnings("deprecation")
    public static String encode(String text) {
        return URLEncoder.encode(text);
    }

    /** As {@link #encode(String)} */
    @SuppressWarnings("deprecation")
    public static String decode(String text) {
        return URLDecoder.decode(text);
    }

    /** returns the protocol (e.g. http) if one appears to be specified, or else null;
     * 'protocol' here should consist of 2 or more _letters_ only followed by a colon
     * (2 required to prevent {@code c:\xxx} being treated as a url)
     */
    public static String getProtocol(String url) {
        if (url == null)
            return null;
        int i = 0;
        StringBuilder result = new StringBuilder();
        while (true) {
            if (url.length() <= i)
                return null;
            char c = url.charAt(i);
            if (Character.isLetter(c))
                result.append(c);
            else if (c == ':') {
                if (i >= 2)
                    return result.toString().toLowerCase();
                return null;
            } else
                return null;
            i++;
        }
    }

    /** return the last segment of the given url before any '?', e.g. the filename or last directory name in the case of directories
     * (cf unix `basename`) */
    public static String getBasename(String url) {
        if (url == null)
            return null;
        if (getProtocol(url) != null) {
            int firstQ = url.indexOf('?');
            if (firstQ >= 0)
                url = url.substring(0, firstQ);
        }
        url = Strings.removeAllFromEnd(url, "/");
        return url.substring(url.lastIndexOf('/') + 1);
    }

    public static boolean isDirectory(String fileUrl) {
        File file;
        if (isUrlWithProtocol(fileUrl)) {
            if (getProtocol(fileUrl).equals("file")) {
                file = new File(URI.create(fileUrl));
            } else {
                return false;
            }
        } else {
            file = new File(fileUrl);
        }
        return file.isDirectory();
    }

    public static File toFile(String fileUrl) {
        if (isUrlWithProtocol(fileUrl)) {
            if (getProtocol(fileUrl).equals("file")) {
                return new File(URI.create(fileUrl));
            } else {
                throw new IllegalArgumentException("Not a file protocol URL: " + fileUrl);
            }
        } else {
            return new File(fileUrl);
        }
    }

    /** as {@link #asDataUrlBase64(String)} with plain text */
    public static String asDataUrlBase64(String data) {
        return asDataUrlBase64(MediaType.PLAIN_TEXT_UTF_8, data.getBytes());
    }

    /** 
     * Creates a "data:..." scheme URL for use with supported parsers, using Base64 encoding.
     * (But note, by default Java's URL is not one of them, although Brooklyn's ResourceUtils does support it.)
     * <p>
     * It is not necessary (at least for Brookyn's routines) to base64 encode it, but recommended as that is likely more
     * portable and easier to work with if odd characters are included.
     * <p>
     * It is worth noting that Base64 uses '+' which can be replaced by ' ' in some URL parsing.  
     * But in practice it does not seem to cause issues.
     * An alternative is to use {@link BaseEncoding#base64Url()} but it is not clear how widely that is supported
     * (nor what parameter should be given to indicate that type of encoding, as the spec calls for 'base64'!)
     * <p>
     * null type means no type info will be included in the URL. */
    public static String asDataUrlBase64(MediaType type, byte[] bytes) {
        return "data:" + (type != null ? type.withoutParameters().toString() : "") + ";base64,"
                + new String(BaseEncoding.base64().encode(bytes));
    }

}