[jOOQ/jOOQ#9725] Move org.jooq.meta.tools.FilePattern API to core library

This commit is contained in:
Lukas Eder 2020-01-14 14:35:13 +01:00
parent 1d22290407
commit 025fc98685
9 changed files with 410 additions and 201 deletions

View File

@ -41,15 +41,16 @@ import static org.jooq.conf.SettingsTools.renderLocale;
import static org.jooq.impl.DSL.name;
import static org.jooq.tools.StringUtils.isBlank;
import java.io.File;
import java.io.Reader;
import java.sql.SQLException;
import java.util.Comparator;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jooq.DSLContext;
import org.jooq.FilePattern;
import org.jooq.FilePattern.Loader;
import org.jooq.FilePattern.Sort;
import org.jooq.Name;
import org.jooq.Name.Quoted;
import org.jooq.Queries;
@ -64,8 +65,6 @@ import org.jooq.impl.DSL;
import org.jooq.impl.DefaultVisitListener;
import org.jooq.impl.ParserException;
import org.jooq.meta.extensions.AbstractInterpretingDatabase;
import org.jooq.meta.tools.FilePattern;
import org.jooq.meta.tools.FilePattern.Loader;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.jdbc.JDBCUtils;
@ -99,8 +98,6 @@ public class DDLDatabase extends AbstractInterpretingDatabase {
String parseIgnoreCommentStart = getProperties().getProperty("parseIgnoreCommentStart", defaultSettings.getParseIgnoreCommentStart());
String parseIgnoreCommentStop = getProperties().getProperty("parseIgnoreCommentStop", defaultSettings.getParseIgnoreCommentStop());
Comparator<File> fileComparator = FilePattern.fileComparator(sort);
if (isBlank(scripts)) {
scripts = "";
log.warn("No scripts defined", "It is recommended that you provide an explicit script directory to scan");
@ -146,7 +143,11 @@ public class DDLDatabase extends AbstractInterpretingDatabase {
});
}
FilePattern.load(encoding, scripts, fileComparator, new Loader() {
new FilePattern()
.encoding(encoding)
.pattern(scripts)
.sort(Sort.of(sort))
.load(new Loader() {
@Override
public void load(Source source) {
DDLDatabase.this.load(ctx, source);

View File

@ -1,180 +0,0 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.meta.tools;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;
import org.jooq.Internal;
import org.jooq.Source;
import org.jooq.tools.JooqLogger;
/**
* A utility class that can traverse a directory structure given some ant-style
* file patterns.
* <p>
* This is INTERNAL API. Please do not use directly as API may change
* incompatibly.
*
* @author Lukas Eder
*/
@Internal
public final class FilePattern {
private static final JooqLogger log = JooqLogger.getLogger(FilePattern.class);
public static final Comparator<File> fileComparator(String sort) {
if ("alphanumeric".equals(sort))
return new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.compareTo(o2);
}
};
else if ("none".equals(sort))
return null;
else if ("flyway".equals(sort))
return FlywayFileComparator.INSTANCE;
else
return FileComparator.INSTANCE;
}
/**
* Try loading <code>pattern</code>.
* <p>
* This method tries loading contents from <code>pattern</code> using the
* following algorithm:
* <p>
* <ul>
* <li>If <code>pattern</code> is a valid classpath resource, load it from
* there</li>
* <li>If <code>pattern</code> is a valid file on the file system, load it
* from there</li>
* <li>Match all files on the file system according to <code>pattern</code>
* (interpreted as an ant-style file pattern), and load them</li>
* </ul>
*/
public static final void load(
String encoding,
String pattern,
Comparator<File> fileComparator,
Loader loader
) throws Exception {
boolean loaded = false;
URL url = FilePattern.class.getResource(pattern);
if (url != null) {
log.info("Reading from classpath: " + pattern);
loader.load(Source.of(new File(url.toURI()), encoding));
loaded = true;
}
else {
File file = new File(pattern);
if (file.exists()) {
load(encoding, file, fileComparator, null, loader);
loaded = true;
}
else {
// [#8336] Relative paths aren't necessarily relative to the
// working directory, but maybe to some subdirectory
if (pattern.contains("*") || pattern.contains("?"))
file = new File(pattern.replaceAll("[*?].*", "")).getCanonicalFile();
else
file = new File(".").getCanonicalFile();
Pattern regex = Pattern.compile("^.*?"
+ pattern
.replace("\\", "/")
.replace(".", "\\.")
.replace("?", ".")
.replace("**", ".+?")
.replace("*", "[^/]*")
+ "$"
);
load(encoding, file, fileComparator, regex, loader);
loaded = true;
}
}
if (!loaded)
log.error("Could not find source(s) : " + pattern);
}
private static final void load(
String encoding,
File file,
Comparator<File> fileComparator,
Pattern pattern,
Loader loader
) throws Exception {
if (file.isFile()) {
if (pattern == null || pattern.matcher(file.getCanonicalPath().replace("\\", "/")).matches()) {
log.info("Reading from: " + file + " [*]");
loader.load(Source.of(file, encoding));
}
}
else if (file.isDirectory()) {
log.info("Reading from: " + file);
File[] files = file.listFiles();
if (files != null) {
if (fileComparator != null)
Arrays.sort(files, fileComparator);
for (File f : files)
load(encoding, f, fileComparator, pattern, loader);
}
}
// [#7767] Backtrack to a parent directory in case the current file pattern doesn't match yet
else if (!file.exists() && file.getParentFile() != null) {
load(encoding, file.getParentFile(), fileComparator, pattern, loader);
}
}
public interface Loader {
void load(Source source) throws Exception;
}
}

View File

@ -74,10 +74,14 @@ import javax.xml.transform.stream.StreamSource;
import org.jooq.Constants;
import org.jooq.DSLContext;
import org.jooq.FilePattern;
import org.jooq.FilePattern.Loader;
import org.jooq.FilePattern.Sort;
import org.jooq.Name;
import org.jooq.SQLDialect;
import org.jooq.SortOrder;
import org.jooq.Source;
import org.jooq.exception.IOException;
import org.jooq.impl.DSL;
import org.jooq.meta.AbstractDatabase;
import org.jooq.meta.AbstractIndexDefinition;
@ -100,8 +104,6 @@ import org.jooq.meta.SchemaDefinition;
import org.jooq.meta.SequenceDefinition;
import org.jooq.meta.TableDefinition;
import org.jooq.meta.UDTDefinition;
import org.jooq.meta.tools.FilePattern;
import org.jooq.meta.tools.FilePattern.Loader;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.jdbc.JDBCUtils;
@ -151,9 +153,9 @@ public class XMLDatabase extends AbstractDatabase {
throw new RuntimeException("Must provide an xmlFile property");
try {
FilePattern.load("UTF-8", xml, FilePattern.fileComparator(sort), new Loader() {
new FilePattern().pattern(xml).sort(Sort.of(sort)).load(new Loader() {
@Override
public void load(Source source) throws Exception {
public void load(Source source) {
String content;
Reader reader = null;
@ -197,6 +199,9 @@ public class XMLDatabase extends AbstractDatabase {
transformer.transform(new StreamSource(reader), new StreamResult(writer));
content = writer.getBuffer().toString();
}
catch (java.io.IOException e) {
throw new IOException("Error while loading XSL file", e);
}
catch (TransformerException e) {
throw new RuntimeException("Error while transforming XML file " + xml + " with XSL file " + xsl, e);
}

View File

@ -35,7 +35,7 @@
*
*
*/
package org.jooq.meta.tools;
package org.jooq;
import java.io.File;
import java.util.Comparator;
@ -62,7 +62,7 @@ import java.util.Comparator;
*
* @author Lukas Eder
*/
public final class FileComparator implements Comparator<File> {
final class FileComparator implements Comparator<File> {
public static final FileComparator INSTANCE = new FileComparator();

View File

@ -0,0 +1,364 @@
/*
* 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq;
import static org.jooq.FilePattern.Sort.SEMANTIC;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import org.jooq.exception.IOException;
import org.jooq.tools.JooqLogger;
/**
* A utility class that can traverse a directory structure given some ant-style
* file patterns, or classpath resources.
* <p>
* The following algorithm is applied when traversing sources:
* <p>
* <ul>
* <li>If <code>pattern</code> is a valid classpath resource, load the single
* {@link Source} from there</li>
* <li>If <code>pattern</code> is a valid file on the file system, load the
* single {@link Source} from there</li>
* <li>Match all files on the file system according to <code>pattern</code>
* (interpreted as an ant-style file pattern), and load all of the
* {@link Source} items given {@link #sort()}. An example pattern is
* <code>src/main/resources/&#42;&#42;/&#42;.sql</code></li>
* </ul>
* <p>
* This is INTERNAL API. Please do not use directly as API may change
* incompatibly.
*
* @author Lukas Eder
*/
@Internal
public final class FilePattern {
private static final JooqLogger log = JooqLogger.getLogger(FilePattern.class);
private final Sort sort;
private final Comparator<File> comparator;
private final File basedir;
private final String pattern;
private final String encoding;
public FilePattern() {
this(
(Sort) null,
(File) null,
"**",
"UTF-8"
);
}
private FilePattern(
Sort sort,
File basedir,
String pattern,
String encoding
) {
this.sort = sort;
this.comparator = fileComparator(sort);
this.basedir = basedir == null ? new File(".") : basedir;
this.pattern = pattern;
this.encoding = encoding;
}
public final Sort sort() {
return sort;
}
public final FilePattern sort(Sort newSort) {
return new FilePattern(
newSort,
basedir,
pattern,
encoding
);
}
public final File basedir() {
return basedir;
}
public final FilePattern basedir(File newBasedir) {
return new FilePattern(
sort,
newBasedir,
pattern,
encoding
);
}
public final String pattern() {
return pattern;
}
public final FilePattern pattern(String newPattern) {
return new FilePattern(
sort,
basedir,
newPattern,
encoding
);
}
public final String encoding() {
return encoding;
}
public final FilePattern encoding(String newEncoding) {
return new FilePattern(
sort,
basedir,
pattern,
newEncoding
);
}
private static final Comparator<File> fileComparator(Sort sort) {
if (sort == null)
sort = SEMANTIC;
switch (sort) {
case ALPHANUMERIC:
return new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.compareTo(o2);
}
};
case NONE:
return null;
case FLYWAY:
return FlywayFileComparator.INSTANCE;
case SEMANTIC:
return FileComparator.INSTANCE;
default:
throw new IllegalArgumentException("Unsupported sort: " + sort);
}
}
/**
* Retrieve a set of {@link Source} items from this pattern.
*
* @throws IOException if something goes wrong while loading file contents.
*/
public final List<Source> collect() {
final List<Source> list = new ArrayList<>();
load(new Loader() {
@Override
public void load(Source source) {
list.add(source);
}
});
return list;
}
/**
* Load a set of {@link Source} items from this pattern.
*
* @throws IOException if something goes wrong while loading file contents.
*/
public final void load(Loader loader) {
boolean loaded = false;
URL url = FilePattern.class.getResource(pattern);
try {
if (url != null) {
log.info("Reading from classpath: " + pattern);
loader.load(Source.of(new File(url.toURI()), encoding));
loaded = true;
}
else {
File file = new File(pattern);
if (file.exists()) {
load(file, comparator, null, loader);
loaded = true;
}
else {
// [#8336] Relative paths aren't necessarily relative to the
// working directory, but maybe to some subdirectory
if (pattern.contains("*") || pattern.contains("?")) {
String prefix = pattern.replaceAll("[*?].*", "");
File canonical = new File(prefix).getCanonicalFile();
if (canonical.exists())
file = canonical;
else
file = new File(basedir, prefix);
}
else
file = basedir.getCanonicalFile();
Pattern regex = Pattern.compile("^.*?"
+ pattern
.replace("\\", "/")
.replace(".", "\\.")
.replace("?", ".")
.replace("**", ".+?")
.replace("*", "[^/]*")
+ "$"
);
load(file, comparator, regex, loader);
loaded = true;
}
}
}
// It is quite unlikely that a classpath URL doesn't produce a valid URI
catch (URISyntaxException e) {
throw new RuntimeException(e);
}
catch (java.io.IOException e) {
throw new IOException("Error while loading pattern", e);
}
if (!loaded)
log.error("Could not find source(s) : " + pattern);
}
private final void load(
File file,
Comparator<File> fileComparator,
Pattern regex,
Loader loader
) throws java.io.IOException {
if (file.isFile()) {
if (regex == null || regex.matcher(file.getCanonicalPath().replace("\\", "/")).matches()) {
log.info("Reading from: " + file + " [*]");
loader.load(Source.of(file, encoding));
}
}
else if (file.isDirectory()) {
log.info("Reading from: " + file);
File[] files = file.listFiles();
if (files != null) {
if (fileComparator != null)
Arrays.sort(files, fileComparator);
for (File f : files)
load(f, comparator, regex, loader);
}
}
// [#7767] Backtrack to a parent directory in case the current file pattern doesn't match yet
else if (!file.exists() && file.getParentFile() != null) {
load(file.getParentFile(), comparator, regex, loader);
}
}
/**
* A callback interface that allows for loading a {@link Source}.
*/
@FunctionalInterface
public interface Loader {
void load(Source source);
}
/**
* The sort algorithm to be applied to directory contents.
*/
public enum Sort {
/**
* Semantic, version aware sorting (the default).
* <p>
* For example:
*
* <pre>
* version-1
* version-2
* version-10
* </pre>
*/
SEMANTIC,
/**
* Standard alphanumeric sorting.
* <p>
* For example:
*
* <pre>
* version-1
* version-10
* version-2
* </pre>
*/
ALPHANUMERIC,
/**
* Flyway compatible sorting.
*/
FLYWAY,
/**
* No explicit sorting (may be non deterministic, depending on the file
* system).
*/
NONE;
public static final Sort of(String sort) {
if ("alphanumeric".equals(sort))
return ALPHANUMERIC;
else if ("none".equals(sort))
return NONE;
else if ("flyway".equals(sort))
return FLYWAY;
else
return SEMANTIC;
}
}
}

View File

@ -35,7 +35,7 @@
*
*
*/
package org.jooq.meta.tools;
package org.jooq;
import java.math.BigInteger;
import java.util.Comparator;
@ -63,7 +63,7 @@ import java.util.regex.Pattern;
*
* @author Lukas Eder
*/
public final class FilenameComparator implements Comparator<String> {
final class FilenameComparator implements Comparator<String> {
// Idea taken from here: https://codereview.stackexchange.com/a/37217/5314
private static final Pattern NUMBERS = Pattern.compile("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");

View File

@ -35,11 +35,14 @@
*
*
*/
package org.jooq.meta.tools;
package org.jooq;
import java.io.File;
import java.util.Comparator;
/**
* @author Lukas Eder
*/
final class FlywayFileComparator implements Comparator<File> {
public static final FlywayFileComparator INSTANCE = new FlywayFileComparator();

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jooq.meta.tools;
package org.jooq;
import java.math.BigInteger;
import java.util.ArrayList;
@ -65,11 +65,11 @@ final class FlywayVersion implements Comparable<FlywayVersion> {
*/
private FlywayVersion(String version) {
if (!StringUtils.isEmpty(version)) {
String normalizedVersion = version.replace('_', '.');
this.versionParts = tokenize(normalizedVersion);
String normalizedVersion = version.replace('_', '.');
this.versionParts = tokenize(normalizedVersion);
}
else {
this.versionParts = new ArrayList<>();
this.versionParts = new ArrayList<>();
}
}
@ -140,4 +140,4 @@ final class FlywayVersion implements Comparable<FlywayVersion> {
return parts;
}
}
}

View File

@ -249,4 +249,20 @@ public final class Source {
else
return new InputStreamReader(is);
}
@Override
public String toString() {
if (string != null)
return string;
else if (bytes != null)
return readString();
else if (reader != null)
return "Source (Reader)";
else if (inputStream != null)
return "Source (InputStream)";
else if (file != null)
return "Source (" + file + ")";
else
return "Source (other)";
}
}