blob: 6aaf494abadda953bdb79e4d459051c9c39ed80a [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.freemarker.generator.base.template;
import org.apache.freemarker.generator.base.FreeMarkerConstants.Location;
import org.apache.freemarker.generator.base.datasource.DataSource;
import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
import org.apache.freemarker.generator.base.file.RecursiveFileSupplier;
import org.apache.freemarker.generator.base.util.NonClosableWriterWrapper;
import org.apache.freemarker.generator.base.util.StringUtils;
import org.apache.freemarker.generator.base.util.Validate;
import java.io.BufferedWriter;
import java.io.File;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.singletonList;
/**
* Maps user-supplied templates (interactive, files, directories, paths) to a
* list of TemplateTransformation.
*/
public class TemplateTransformationsBuilder {
/** Interactive template */
private TemplateSource interactiveTemplate;
/** Template source - either a single template or a directory */
private String templateSource;
/** Optional include patterns for resolving source templates or template directories */
private final List<String> includes;
/** Optional exclude patterns for resolving source templates or template directories */
private final List<String> excludes;
/** Encoding used to load templates */
private Charset templateEncoding;
/** Optional output file or directory - if none is defined everything is written to STDOUT */
private String output;
/** Optional output encoding */
private Charset outputEncoding;
/** Optional caller-supplied writer used for testing */
private Writer callerSuppliedWriter;
private TemplateTransformationsBuilder() {
this.templateSource = null;
this.includes = new ArrayList<>();
this.excludes = new ArrayList<>();
this.templateEncoding = UTF_8;
this.output = null;
this.callerSuppliedWriter = null;
this.outputEncoding = UTF_8;
}
public static TemplateTransformationsBuilder builder() {
return new TemplateTransformationsBuilder();
}
public List<TemplateTransformation> build() {
validate();
final List<TemplateTransformation> result = new ArrayList<>();
final File outputFile = getOutputFile();
if (hasInteractiveTemplate()) {
result.add(resolveInteractiveTemplate(outputFile));
} else {
result.addAll(resolve(templateSource, outputFile));
}
return result;
}
public TemplateTransformationsBuilder setInteractiveTemplate(String code) {
if (StringUtils.isNotEmpty(code)) {
this.interactiveTemplate = TemplateSource.fromCode(Location.INTERACTIVE, code);
}
return this;
}
public TemplateTransformationsBuilder setTemplateSource(String source) {
this.templateSource = source;
return this;
}
public TemplateTransformationsBuilder addInclude(String include) {
if (StringUtils.isNotEmpty(include)) {
this.includes.add(include);
}
return this;
}
public TemplateTransformationsBuilder addExclude(String exclude) {
if (StringUtils.isNotEmpty(exclude)) {
this.excludes.add(exclude);
}
return this;
}
public TemplateTransformationsBuilder setTemplateEncoding(Charset charset) {
if (charset != null) {
this.templateEncoding = charset;
}
return this;
}
public TemplateTransformationsBuilder setOutput(String output) {
this.output = output;
return this;
}
public TemplateTransformationsBuilder setOutputEncoding(Charset outputEncoding) {
if (outputEncoding != null) {
// keep UTF-8 here
this.outputEncoding = outputEncoding;
}
return this;
}
public TemplateTransformationsBuilder setCallerSuppliedWriter(Writer callerSuppliedWriter) {
this.callerSuppliedWriter = callerSuppliedWriter;
return this;
}
private void validate() {
Validate.isTrue(interactiveTemplate == null || templateSource == null, "No template was provided");
}
/**
* Resolve a <code>source</code> to a list of <code>TemplateTransformation</code>.
*
* @param source the source being a file name, an URI or <code>NamedUri</code>
* @param output Optional output file or directory
* @return list of <code>TemplateTransformation</code>
*/
private List<TemplateTransformation> resolve(String source, File output) {
if (isTemplateFileFound(source)) {
return resolveTemplateFile(source, output);
} else if (isTemplateDirectoryFound(source)) {
return resolveTemplateDirectory(source, output);
} else if (isTemplateHttpUrl(source)) {
return resolveTemplateHttpUrl(source, output);
} else if (isTemplateUri(source)) {
return resolveTemplateUri(source, output);
} else {
return resolveTemplatePath(source, output);
}
}
private List<TemplateTransformation> resolveTemplateFile(String source, File outputFile) {
final TemplateSource templateSource = templateSource(source);
final TemplateOutput templateOutput = templateOutput(outputFile);
return singletonList(new TemplateTransformation(templateSource, templateOutput));
}
private List<TemplateTransformation> resolveTemplateDirectory(String source, File outputDirectory) {
Validate.fileExists(new File(source), "Template directory does not exist: " + source);
final File templateDirectory = new File(source);
final List<File> templateFiles = templateFilesSupplier(source, getInclude(), getExclude()).get();
final List<TemplateTransformation> templateTransformations = new ArrayList<>();
for (File templateFile : templateFiles) {
final TemplateSource templateSource = templateSource(templateFile.getAbsolutePath());
final File outputFile = getTemplateOutputFile(templateDirectory, templateFile, outputDirectory);
final TemplateOutput templateOutput = templateOutput(outputFile);
templateTransformations.add(new TemplateTransformation(templateSource, templateOutput));
}
return templateTransformations;
}
private List<TemplateTransformation> resolveTemplateHttpUrl(String source, File out) {
final TemplateSource templateSource = templateSource(source);
final TemplateOutput templateOutput = templateOutput(out);
return singletonList(new TemplateTransformation(templateSource, templateOutput));
}
private List<TemplateTransformation> resolveTemplateUri(String source, File out) {
final TemplateSource templateSource = templateSource(source);
final TemplateOutput templateOutput = templateOutput(out);
return singletonList(new TemplateTransformation(templateSource, templateOutput));
}
private List<TemplateTransformation> resolveTemplatePath(String source, File out) {
final TemplateSource templateSource = TemplateSource.fromPath(source, templateEncoding);
final TemplateOutput templateOutput = templateOutput(out);
return singletonList(new TemplateTransformation(templateSource, templateOutput));
}
private TemplateTransformation resolveInteractiveTemplate(File out) {
final TemplateOutput templateOutput = templateOutput(out);
return new TemplateTransformation(interactiveTemplate, templateOutput);
}
private TemplateOutput templateOutput(File templateOutputFile) {
if (callerSuppliedWriter != null) {
return TemplateOutput.fromWriter(callerSuppliedWriter);
} else if (templateOutputFile != null) {
return TemplateOutput.fromFile(templateOutputFile, outputEncoding);
} else {
return TemplateOutput.fromWriter(stdoutWriter(outputEncoding));
}
}
private TemplateSource templateSource(String source) {
try (DataSource dataSource = DataSourceFactory.create(source)) {
return TemplateSource.fromCode(dataSource.getName(), dataSource.getText(templateEncoding.name()));
}
}
private String getInclude() {
return includes.isEmpty() ? null : includes.get(0);
}
private String getExclude() {
return excludes.isEmpty() ? null : excludes.get(0);
}
private boolean hasInteractiveTemplate() {
return interactiveTemplate != null;
}
private File getOutputFile() {
return output == null ? null : new File(output);
}
private static File getTemplateOutputFile(File templateDirectory, File templateFile, File outputDirectory) {
if (outputDirectory == null) {
// missing output directory uses STDOUT
return null;
}
final String relativePath = relativePath(templateDirectory, templateFile);
final String relativeOutputFileName = mapExtension(relativePath);
return new File(outputDirectory, relativeOutputFileName);
}
private static boolean isTemplateFileFound(String source) {
final File file = new File(source);
return file.exists() && file.isFile();
}
private static boolean isTemplateDirectoryFound(String source) {
final File file = new File(source);
return file.exists() && file.isDirectory();
}
private static boolean isTemplateHttpUrl(String source) {
return source.contains("http://") || source.contains("https://");
}
private static boolean isTemplateUri(String source) {
return source.contains("://");
}
private static RecursiveFileSupplier templateFilesSupplier(String source, String include, String exclude) {
return new RecursiveFileSupplier(singletonList(source), singletonList(include), singletonList(exclude));
}
private static String relativePath(File directory, File file) {
return file.getAbsolutePath()
.substring(directory.getAbsolutePath().length())
.substring(1);
}
private static String mapExtension(String fileName) {
if (fileName.toLowerCase().endsWith(".ftl")) {
return fileName.substring(0, fileName.length() - 4);
} else {
return fileName;
}
}
private Writer stdoutWriter(Charset outputEncoding) {
// avoid closing System.out after rendering the template
return new BufferedWriter(new NonClosableWriterWrapper(new OutputStreamWriter(System.out, outputEncoding)));
}
}