blob: 46762ea25245560ddb4b213de946a125637e3e55 [file] [log] [blame]
/*
* 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.calcite.avatica.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
/**
* Utilities for {@link Source}.
*/
public abstract class Sources {
private Sources() {}
public static Source of(File file) {
return new FileSource(file);
}
public static Source of(URL url) {
return new FileSource(url);
}
public static Source file(File baseDirectory, String fileName) {
final File file = new File(fileName);
if (baseDirectory != null && !file.isAbsolute()) {
return of(new File(baseDirectory, fileName));
} else {
return of(file);
}
}
public static Source url(String url) {
try {
return of(new URL(url));
} catch (MalformedURLException | IllegalArgumentException e) {
throw new RuntimeException("Malformed URL: '" + url + "'", e);
}
}
/** Looks for a suffix on a path and returns
* either the path with the suffix removed
* or null. */
private static String trimOrNull(String s, String suffix) {
return s.endsWith(suffix)
? s.substring(0, s.length() - suffix.length())
: null;
}
private static boolean isFile(Source source) {
return source.protocol().equals("file");
}
/** Implementation of {@link Source} on the top of a {@link File} or
* {@link URL}. */
private static class FileSource implements Source {
private final File file;
private final URL url;
/**
* A flag indicating if the url is deduced from the file object.
*/
private final boolean urlGenerated;
private FileSource(URL url) {
this.url = Objects.requireNonNull(url, "url");
this.file = urlToFile(url);
this.urlGenerated = false;
}
private FileSource(File file) {
this.file = Objects.requireNonNull(file, "file");
this.url = fileToUrl(file);
this.urlGenerated = true;
}
private File fileNonNull() {
return Objects.requireNonNull(file, "file");
}
private static File urlToFile(URL url) {
if (!"file".equals(url.getProtocol())) {
return null;
}
URI uri;
try {
uri = url.toURI();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Unable to convert URL " + url + " to URI", e);
}
if (uri.isOpaque()) {
// It is like file:test%20file.c++
// getSchemeSpecificPart would return "test file.c++"
return new File(uri.getSchemeSpecificPart());
}
// See https://stackoverflow.com/a/17870390/1261287
return Paths.get(uri).toFile();
}
private static URL fileToUrl(File file) {
String filePath = file.getPath();
if (!file.isAbsolute()) {
// convert relative file paths
filePath = filePath.replace(File.separatorChar, '/');
if (file.isDirectory() && !filePath.endsWith("/")) {
filePath += "/";
}
try {
// We need to encode path. For instance, " " should become "%20"
// That is why java.net.URLEncoder.encode(java.lang.String, java.lang.String) is not
// suitable because it replaces " " with "+".
String encodedPath = new URI(null, null, filePath, null).getRawPath();
return new URL("file", null, 0, encodedPath);
} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalArgumentException("Unable to create URL for file " + filePath, e);
}
}
URI uri = null;
try {
// convert absolute file paths
uri = file.toURI();
return uri.toURL();
} catch (SecurityException e) {
throw new IllegalArgumentException("No access to the underlying file " + filePath, e);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Unable to convert URI " + uri + " to URL", e);
}
}
@Override public String toString() {
return (urlGenerated ? fileNonNull() : url).toString();
}
@Override public URL url() {
return url;
}
@Override public File file() {
if (file == null) {
throw new UnsupportedOperationException();
}
return file;
}
@Override public String protocol() {
return file != null ? "file" : url.getProtocol();
}
@Override public String path() {
if (file != null) {
return file.getPath();
}
try {
// Decode %20 and friends
return url.toURI().getSchemeSpecificPart();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Unable to convert URL " + url + " to URI", e);
}
}
@Override public Reader reader() throws IOException {
final InputStream is;
if (path().endsWith(".gz")) {
final InputStream fis = openStream();
is = new GZIPInputStream(fis);
} else {
is = openStream();
}
return new InputStreamReader(is, StandardCharsets.UTF_8);
}
@Override public InputStream openStream() throws IOException {
if (file != null) {
return new FileInputStream(file);
} else {
return url.openStream();
}
}
@Override public Source trim(String suffix) {
Source x = trimOrNull(suffix);
return x == null ? this : x;
}
@Override public Source trimOrNull(String suffix) {
if (!urlGenerated) {
final String s = Sources.trimOrNull(url.toExternalForm(), suffix);
return s == null ? null : Sources.url(s);
} else {
final String s = Sources.trimOrNull(fileNonNull().getPath(), suffix);
return s == null ? null : of(new File(s));
}
}
@Override public Source append(Source child) {
if (isFile(child)) {
if (child.file().isAbsolute()) {
return child;
}
} else {
try {
URI uri = child.url().toURI();
if (!uri.isOpaque()) {
// The URL is "absolute" (it starts with a slash)
return child;
}
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Unable to convert URL " + child.url() + " to URI", e);
}
}
String path = child.path();
if (!urlGenerated) {
String encodedPath = new File(".").toURI().relativize(new File(path).toURI())
.getRawSchemeSpecificPart();
return Sources.url(url + "/" + encodedPath);
} else {
return Sources.file(file, path);
}
}
@Override public Source relative(Source parent) {
if (isFile(parent)) {
if (isFile(this)
&& fileNonNull().getPath().startsWith(parent.file().getPath())) {
String rest = fileNonNull().getPath().substring(parent.file().getPath().length());
if (rest.startsWith(File.separator)) {
return Sources.file(null, rest.substring(File.separator.length()));
}
}
return this;
} else {
if (!isFile(this)) {
String rest = Sources.trimOrNull(url.toExternalForm(),
parent.url().toExternalForm());
if (rest != null
&& rest.startsWith("/")) {
return Sources.file(null, rest.substring(1));
}
}
return this;
}
}
}
}
// End Sources.java