/*
* Copyright 2010-2020 Redgate Software Ltd
*
* Licensed 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.flywaydb.core.api;
import org.flywaydb.core.api.logging.Log;
import org.flywaydb.core.api.logging.LogFactory;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
A location to load migrations from.
/**
* A location to load migrations from.
*/
public final class Location implements Comparable<Location> {
private static final Log LOG = LogFactory.getLog(Location.class);
The prefix for classpath locations.
/**
* The prefix for classpath locations.
*/
private static final String CLASSPATH_PREFIX = "classpath:";
The prefix for filesystem locations.
/**
* The prefix for filesystem locations.
*/
public static final String FILESYSTEM_PREFIX = "filesystem:";
The prefix part of the location. Can be either classpath: or filesystem:.
/**
* The prefix part of the location. Can be either classpath: or filesystem:.
*/
private final String prefix;
The path part of the location.
/**
* The path part of the location.
*/
private String rawPath;
The first folder in the path. This will equal rawPath if the path does not contain any wildcards
/**
* The first folder in the path. This will equal rawPath if the path does not contain any wildcards
*/
private String rootPath;
private Pattern pathRegex = null;
Creates a new location.
Params: - descriptor – The location descriptor.
/**
* Creates a new location.
*
* @param descriptor The location descriptor.
*/
public Location(String descriptor) {
String normalizedDescriptor = descriptor.trim();
if (normalizedDescriptor.contains(":")) {
prefix = normalizedDescriptor.substring(0, normalizedDescriptor.indexOf(":") + 1);
rawPath = normalizedDescriptor.substring(normalizedDescriptor.indexOf(":") + 1);
} else {
prefix = CLASSPATH_PREFIX;
rawPath = normalizedDescriptor;
}
if (isClassPath()) {
if (rawPath.contains(".")) {
LOG.warn("Use of dots (.) as path separators will be deprecated in Flyway 7. Path: " + rawPath);
}
rawPath = rawPath.replace(".", "/");
if (rawPath.startsWith("/")) {
rawPath = rawPath.substring(1);
}
if (rawPath.endsWith("/")) {
rawPath = rawPath.substring(0, rawPath.length() - 1);
}
processRawPath();
} else if (isFileSystem()) {
processRawPath();
rootPath = new File(rootPath).getPath();
if (pathRegex == null) {
// if the original path contained no wildcards, also normalise it
rawPath = new File(rawPath).getPath();
}
} else {
throw new FlywayException("Unknown prefix for location (should be either filesystem: or classpath:): "
+ normalizedDescriptor);
}
if (rawPath.endsWith(File.separator)) {
rawPath = rawPath.substring(0, rawPath.length() - 1);
}
}
Process the rawPath into a rootPath and a regex.
Supported wildcards:
**: Match any 0 or more directories
*: Match any sequence of non-seperator characters
?: Match any single character
/**
* Process the rawPath into a rootPath and a regex.
* Supported wildcards:
* **: Match any 0 or more directories
* *: Match any sequence of non-seperator characters
* ?: Match any single character
*/
private void processRawPath() {
if (rawPath.contains("*") || rawPath.contains("?")) {
// we need to figure out the root, and create the regex
String seperator = isFileSystem() ? File.separator : "/";
String escapedSeperator = seperator.replace("\\", "\\\\").replace("/", "\\/");
// split on either of the path seperators
String[] pathSplit = rawPath.split("[\\\\/]");
StringBuilder rootPart = new StringBuilder();
StringBuilder patternPart = new StringBuilder();
boolean endsInFile = false;
boolean skipSeperator = false;
boolean inPattern = false;
for (String pathPart : pathSplit) {
endsInFile = false;
if (pathPart.contains("*") || pathPart.contains("?")) {
inPattern = true;
}
if (inPattern) {
if (skipSeperator) {
skipSeperator = false;
} else {
patternPart.append("/");
}
String regex;
if ("**".equals(pathPart)) {
regex = "([^/]+/)*?";
// this pattern contains the ending seperator, so make sure we skip appending it after
skipSeperator = true;
} else {
endsInFile = pathPart.contains(".");
regex = pathPart;
regex = regex.replace(".", "\\.");
regex = regex.replace("?", "[^/]");
regex = regex.replace("*", "[^/]+?");
}
patternPart.append(regex);
} else {
rootPart.append(seperator).append(pathPart);
}
}
// We always append a seperator before each part, so ensure we skip it when setting the final rootPath
rootPath = rootPart.length() > 0 ? rootPart.toString().substring(1) : "";
// Again, skip first seperator
String pattern = patternPart.toString().substring(1);
// Replace the temporary / with the actual escaped seperator
pattern = pattern.replace("/", escapedSeperator);
// Append the rootpath if it is non-empty
if (rootPart.length() > 0) {
pattern = rootPath.replace(seperator, escapedSeperator) + escapedSeperator + pattern;
}
// if the path did not end in a file, then append the file match pattern
if (!endsInFile) {
pattern = pattern + escapedSeperator + "(?<relpath>.*)";
}
pathRegex = Pattern.compile(pattern);
} else {
rootPath = rawPath;
}
}
Returns: Whether the given path matches this locations regex. Will always return true when the location did not contain any wildcards.
/**
* @return Whether the given path matches this locations regex. Will always return true when the location did not contain any wildcards.
*/
public boolean matchesPath(String path) {
if (pathRegex == null) {
return true;
}
return pathRegex.matcher(path).matches();
}
Returns the path relative to this location. If the location path contains wildcards, the returned path will be relative
to the last non-wildcard folder in the path.
Returns: the path relative to this location
/**
* Returns the path relative to this location. If the location path contains wildcards, the returned path will be relative
* to the last non-wildcard folder in the path.
* @return the path relative to this location
*/
public String getPathRelativeToThis(String path) {
if (pathRegex != null && pathRegex.pattern().contains("?<relpath>")) {
Matcher matcher = pathRegex.matcher(path);
if (matcher.matches()) {
String relPath = matcher.group("relpath");
if (relPath != null && relPath.length() > 0) {
return relPath;
}
}
}
return rootPath.length() > 0 ? path.substring(rootPath.length() + 1) : path;
}
Checks whether this denotes a location on the classpath.
Returns: true
if it does, false
if it doesn't.
/**
* Checks whether this denotes a location on the classpath.
*
* @return {@code true} if it does, {@code false} if it doesn't.
*/
public boolean isClassPath() {
return CLASSPATH_PREFIX.equals(prefix);
}
Checks whether this denotes a location on the filesystem.
Returns: true
if it does, false
if it doesn't.
/**
* Checks whether this denotes a location on the filesystem.
*
* @return {@code true} if it does, {@code false} if it doesn't.
*/
public boolean isFileSystem() {
return FILESYSTEM_PREFIX.equals(prefix);
}
Checks whether this location is a parent of this other location.
Params: - other – The other location.
Returns: true
if it is, false
if it isn't.
/**
* Checks whether this location is a parent of this other location.
*
* @param other The other location.
* @return {@code true} if it is, {@code false} if it isn't.
*/
@SuppressWarnings("SimplifiableIfStatement")
public boolean isParentOf(Location other) {
if (pathRegex != null || other.pathRegex != null) {
return false;
}
if (isClassPath() && other.isClassPath()) {
return (other.getDescriptor() + "/").startsWith(getDescriptor() + "/");
}
if (isFileSystem() && other.isFileSystem()) {
return (other.getDescriptor() + File.separator).startsWith(getDescriptor() + File.separator);
}
return false;
}
Returns: The prefix part of the location. Can be either classpath: or filesystem:.
/**
* @return The prefix part of the location. Can be either classpath: or filesystem:.
*/
public String getPrefix() {
return prefix;
}
Returns: The root part of the path part of the location.
/**
* @return The root part of the path part of the location.
*/
public String getRootPath() {
return rootPath;
}
Returns: The path part of the location.
/**
* @return The path part of the location.
*/
public String getPath() {
return rawPath;
}
Returns: The the regex that matches in original path. Null if the original path did not contain any wildcards.
/**
* @return The the regex that matches in original path. Null if the original path did not contain any wildcards.
*/
public Pattern getPathRegex() {
return pathRegex;
}
Returns: The complete location descriptor.
/**
* @return The complete location descriptor.
*/
public String getDescriptor() {
return prefix + rawPath;
}
@SuppressWarnings("NullableProblems")
public int compareTo(Location o) {
return getDescriptor().compareTo(o.getDescriptor());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Location location = (Location) o;
return getDescriptor().equals(location.getDescriptor());
}
@Override
public int hashCode() {
return getDescriptor().hashCode();
}
Returns: The complete location descriptor.
/**
* @return The complete location descriptor.
*/
@Override
public String toString() {
return getDescriptor();
}
}