diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java index f535eb9411946..b68be67a9f5bf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/tracker/ConfigTrackingWriter.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.regex.Pattern; +import io.quarkus.bootstrap.util.PropertyUtils; import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; import io.quarkus.runtime.LaunchMode; @@ -107,108 +108,6 @@ public static void write(Map readOptions, ConfigTrackingConfig c * @throws IOException in case of a failure */ public static void write(Writer writer, String name, String value) throws IOException { - if (value != null) { - name = toWritableValue(name, true, true); - value = toWritableValue(value, false, true); - writer.write(name); - writer.write("="); - writer.write(value); - writer.write(System.lineSeparator()); - } - } - - /* - * Converts unicodes to encoded \uxxxx and escapes - * special characters with a preceding slash - */ - - /** - * Escapes characters that are expected to be escaped when {@link java.util.Properties} load - * files from disk. - * - * @param str property name or value - * @param escapeSpace whether to escape a whitespace (should be true for property names) - * @param escapeUnicode whether to converts unicodes to encoded \uxxxx - * @return property name or value that can be written to a file - */ - private static String toWritableValue(String str, boolean escapeSpace, boolean escapeUnicode) { - int len = str.length(); - int bufLen = len * 2; - if (bufLen < 0) { - bufLen = Integer.MAX_VALUE; - } - StringBuilder outBuffer = new StringBuilder(bufLen); - - for (int x = 0; x < len; x++) { - char aChar = str.charAt(x); - // Handle common case first, selecting largest block that - // avoids the specials below - if ((aChar > 61) && (aChar < 127)) { - if (aChar == '\\') { - outBuffer.append('\\'); - outBuffer.append('\\'); - continue; - } - outBuffer.append(aChar); - continue; - } - switch (aChar) { - case ' ': - if (x == 0 || escapeSpace) { - outBuffer.append('\\'); - } - outBuffer.append(' '); - break; - case '\t': - outBuffer.append('\\'); - outBuffer.append('t'); - break; - case '\n': - outBuffer.append('\\'); - outBuffer.append('n'); - break; - case '\r': - outBuffer.append('\\'); - outBuffer.append('r'); - break; - case '\f': - outBuffer.append('\\'); - outBuffer.append('f'); - break; - case '=': // Fall through - case ':': // Fall through - case '#': // Fall through - case '!': - outBuffer.append('\\'); - outBuffer.append(aChar); - break; - default: - if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { - outBuffer.append('\\'); - outBuffer.append('u'); - outBuffer.append(toHex((aChar >> 12) & 0xF)); - outBuffer.append(toHex((aChar >> 8) & 0xF)); - outBuffer.append(toHex((aChar >> 4) & 0xF)); - outBuffer.append(toHex(aChar & 0xF)); - } else { - outBuffer.append(aChar); - } - } - } - return outBuffer.toString(); - } - - /** - * Convert a nibble to a hex character - * - * @param nibble the nibble to convert. - */ - private static char toHex(int nibble) { - return hexDigit[(nibble & 0xF)]; + PropertyUtils.store(writer, name, value); } - - /** A table of hex digits */ - private static final char[] hexDigit = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' - }; } diff --git a/core/processor/pom.xml b/core/processor/pom.xml index 924a0ec371883..8427a552eb841 100644 --- a/core/processor/pom.xml +++ b/core/processor/pom.xml @@ -32,6 +32,16 @@ com.fasterxml.jackson.core jackson-databind + + io.quarkus + quarkus-bootstrap-app-model + + + * + * + + + diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java index 665fa57ad594c..3ac39611cfff3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java @@ -76,6 +76,7 @@ import io.quarkus.annotation.processor.generate_doc.ConfigDocItemScanner; import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter; import io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil; +import io.quarkus.bootstrap.util.PropertyUtils; public class ExtensionAnnotationProcessor extends AbstractProcessor { @@ -239,23 +240,23 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) } } - try { - - final FileObject listResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", - "META-INF/quarkus-javadoc.properties"); - try (OutputStream os = listResource.openOutputStream()) { - try (BufferedOutputStream bos = new BufferedOutputStream(os)) { - try (OutputStreamWriter osw = new OutputStreamWriter(bos, StandardCharsets.UTF_8)) { - try (BufferedWriter bw = new BufferedWriter(osw)) { - javaDocProperties.store(bw, Constants.EMPTY); + if (!javaDocProperties.isEmpty()) { + try { + final FileObject listResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", + "META-INF/quarkus-javadoc.properties"); + try (OutputStream os = listResource.openOutputStream()) { + try (BufferedOutputStream bos = new BufferedOutputStream(os)) { + try (OutputStreamWriter osw = new OutputStreamWriter(bos, StandardCharsets.UTF_8)) { + try (BufferedWriter bw = new BufferedWriter(osw)) { + PropertyUtils.store(javaDocProperties, bw); + } } } } + } catch (IOException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write javadoc properties: " + e); + return; } - - } catch (IOException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write javadoc properties: " + e); - return; } try { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java index ae163f7d9581e..5a15d4b205b38 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java @@ -1,8 +1,18 @@ package io.quarkus.bootstrap.util; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Properties; /** * @@ -67,4 +77,166 @@ public static final boolean getBoolean(String name, boolean notFoundValue) { final String value = getProperty(name, (notFoundValue ? TRUE : FALSE)); return value.isEmpty() ? true : Boolean.parseBoolean(value); } + + /** + * Stores properties into a file sorting the keys alphabetically and following + * {@link Properties#store(Writer, String)} format but skipping the timestamp and comments. + * + * @param properties properties to store + * @param file target file + * @throws IOException in case of a failure + */ + public static void store(Properties properties, Path file) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(file)) { + store(properties, writer); + } + } + + /** + * Stores properties into a file sorting the keys alphabetically and following + * {@link Properties#store(Writer, String)} format but skipping the timestamp and comments. + * + * @param properties properties to store + * @param writer target writer + * @throws IOException in case of a failure + */ + public static void store(Properties properties, Writer writer) throws IOException { + final List names = new ArrayList<>(properties.size()); + for (var name : properties.keySet()) { + names.add(name == null ? null : name.toString()); + } + Collections.sort(names); + for (String name : names) { + store(writer, name, properties.getProperty(name)); + } + } + + /** + * Stores a map of strings into a file sorting the keys alphabetically and following + * {@link Properties#store(Writer, String)} format but skipping the timestamp and comments. + * + * @param properties properties to store + * @param file target file + * @throws IOException in case of a failure + */ + public static void store(Map properties, Path file) throws IOException { + final List names = new ArrayList<>(properties.keySet()); + Collections.sort(names); + try (BufferedWriter writer = Files.newBufferedWriter(file)) { + for (String name : names) { + store(writer, name, properties.get(name)); + } + } + } + + /** + * Writes a config option with its value to the target writer, + * possibly applying some transformations, such as character escaping + * prior to writing. + * + * @param writer target writer + * @param name option name + * @param value option value + * @throws IOException in case of a failure + */ + public static void store(Writer writer, String name, String value) throws IOException { + if (value != null) { + name = toWritableValue(name, true, true); + value = toWritableValue(value, false, true); + writer.write(name); + writer.write("="); + writer.write(value); + writer.write(System.lineSeparator()); + } + } + + /** + * Escapes characters that are expected to be escaped when {@link java.util.Properties} load + * files from disk. + * + * @param str property name or value + * @param escapeSpace whether to escape a whitespace (should be true for property names) + * @param escapeUnicode whether to converts unicodes to encoded \uxxxx + * @return property name or value that can be written to a file + */ + private static String toWritableValue(String str, boolean escapeSpace, boolean escapeUnicode) { + int len = str.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuilder outBuffer = new StringBuilder(bufLen); + + for (int x = 0; x < len; x++) { + char aChar = str.charAt(x); + // Handle common case first, selecting largest block that + // avoids the specials below + if ((aChar > 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); + outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch (aChar) { + case ' ': + if (x == 0 || escapeSpace) { + outBuffer.append('\\'); + } + outBuffer.append(' '); + break; + case '\t': + outBuffer.append('\\'); + outBuffer.append('t'); + break; + case '\n': + outBuffer.append('\\'); + outBuffer.append('n'); + break; + case '\r': + outBuffer.append('\\'); + outBuffer.append('r'); + break; + case '\f': + outBuffer.append('\\'); + outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); + outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex((aChar >> 12) & 0xF)); + outBuffer.append(toHex((aChar >> 8) & 0xF)); + outBuffer.append(toHex((aChar >> 4) & 0xF)); + outBuffer.append(toHex(aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + /** + * Convert a nibble to a hex character + * + * @param nibble the nibble to convert. + */ + private static char toHex(int nibble) { + return hexDigit[(nibble & 0xF)]; + } + + /** A table of hex digits */ + private static final char[] hexDigit = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; } diff --git a/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java b/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java index bcde8aacfae7e..0ae05193c4cb1 100644 --- a/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java +++ b/independent-projects/extension-maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java @@ -69,6 +69,7 @@ import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; import io.quarkus.bootstrap.util.DependencyUtils; +import io.quarkus.bootstrap.util.PropertyUtils; import io.quarkus.devtools.project.extensions.ScmInfoProvider; import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.capabilities.CapabilitiesConfig; @@ -388,10 +389,7 @@ public void execute() throws MojoExecutionException { final Path output = outputDirectory.toPath().resolve(BootstrapConstants.META_INF); try { Files.createDirectories(output); - try (BufferedWriter writer = Files - .newBufferedWriter(output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME))) { - props.store(writer, "Generated by extension-descriptor"); - } + PropertyUtils.store(props, output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME)); } catch (IOException e) { throw new MojoExecutionException( "Failed to persist extension descriptor " + output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME),