/*
* 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 org.apache.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 org.apache.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));
}
}