diff --git a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java b/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java index 286e3ae97c..b9ac4d2e4c 100644 --- a/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java +++ b/jOOQ-meta-extensions/src/main/java/org/jooq/meta/extensions/ddl/DDLDatabase.java @@ -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 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); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilePattern.java b/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilePattern.java deleted file mode 100644 index 832b2460f7..0000000000 --- a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilePattern.java +++ /dev/null @@ -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. - *

- * 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 fileComparator(String sort) { - if ("alphanumeric".equals(sort)) - return new Comparator() { - @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 pattern. - *

- * This method tries loading contents from pattern using the - * following algorithm: - *

- *

- */ - public static final void load( - String encoding, - String pattern, - Comparator 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 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; - } -} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java index 694f58cd2f..e7931b1c84 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/xml/XMLDatabase.java @@ -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); } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FileComparator.java b/jOOQ/src/main/java/org/jooq/FileComparator.java similarity index 94% rename from jOOQ-meta/src/main/java/org/jooq/meta/tools/FileComparator.java rename to jOOQ/src/main/java/org/jooq/FileComparator.java index 41adafd996..361f230e67 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FileComparator.java +++ b/jOOQ/src/main/java/org/jooq/FileComparator.java @@ -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 { +final class FileComparator implements Comparator { public static final FileComparator INSTANCE = new FileComparator(); diff --git a/jOOQ/src/main/java/org/jooq/FilePattern.java b/jOOQ/src/main/java/org/jooq/FilePattern.java new file mode 100644 index 0000000000..732a8e5717 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/FilePattern.java @@ -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. + *

+ * The following algorithm is applied when traversing sources: + *

+ *

    + *
  • If pattern is a valid classpath resource, load the single + * {@link Source} from there
  • + *
  • If pattern is a valid file on the file system, load the + * single {@link Source} from there
  • + *
  • Match all files on the file system according to pattern + * (interpreted as an ant-style file pattern), and load all of the + * {@link Source} items given {@link #sort()}. An example pattern is + * src/main/resources/**/*.sql
  • + *
+ *

+ * 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 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 fileComparator(Sort sort) { + if (sort == null) + sort = SEMANTIC; + + switch (sort) { + case ALPHANUMERIC: + return new Comparator() { + @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 collect() { + final List 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 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). + *

+ * For example: + * + *

+         * version-1
+         * version-2
+         * version-10
+         * 
+ */ + SEMANTIC, + + /** + * Standard alphanumeric sorting. + *

+ * For example: + * + *

+         * version-1
+         * version-10
+         * version-2
+         * 
+ */ + 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; + } + } +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilenameComparator.java b/jOOQ/src/main/java/org/jooq/FilenameComparator.java similarity index 96% rename from jOOQ-meta/src/main/java/org/jooq/meta/tools/FilenameComparator.java rename to jOOQ/src/main/java/org/jooq/FilenameComparator.java index effdb4ecf2..afbaae6d5c 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FilenameComparator.java +++ b/jOOQ/src/main/java/org/jooq/FilenameComparator.java @@ -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 { +final class FilenameComparator implements Comparator { // Idea taken from here: https://codereview.stackexchange.com/a/37217/5314 private static final Pattern NUMBERS = Pattern.compile("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)"); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FlywayFileComparator.java b/jOOQ/src/main/java/org/jooq/FlywayFileComparator.java similarity index 97% rename from jOOQ-meta/src/main/java/org/jooq/meta/tools/FlywayFileComparator.java rename to jOOQ/src/main/java/org/jooq/FlywayFileComparator.java index d6c9c5e9de..ae63870416 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FlywayFileComparator.java +++ b/jOOQ/src/main/java/org/jooq/FlywayFileComparator.java @@ -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 { public static final FlywayFileComparator INSTANCE = new FlywayFileComparator(); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FlywayVersion.java b/jOOQ/src/main/java/org/jooq/FlywayVersion.java similarity index 95% rename from jOOQ-meta/src/main/java/org/jooq/meta/tools/FlywayVersion.java rename to jOOQ/src/main/java/org/jooq/FlywayVersion.java index 6f53acd251..a8f15f318c 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/tools/FlywayVersion.java +++ b/jOOQ/src/main/java/org/jooq/FlywayVersion.java @@ -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 { */ 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 { return parts; } -} \ No newline at end of file +} diff --git a/jOOQ/src/main/java/org/jooq/Source.java b/jOOQ/src/main/java/org/jooq/Source.java index 0c1e8a7880..cb04670b32 100644 --- a/jOOQ/src/main/java/org/jooq/Source.java +++ b/jOOQ/src/main/java/org/jooq/Source.java @@ -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)"; + } }