FileAndPathExtension.java
package org.freshmarker.file;
import org.freshmarker.api.BuiltIn;
import org.freshmarker.api.extension.BuiltInProvider;
import org.freshmarker.api.extension.Register;
import org.freshmarker.api.extension.TemplateObjectProviders;
import org.freshmarker.api.extension.support.BuiltInRegister;
import org.freshmarker.core.ProcessContext;
import org.freshmarker.core.ProcessException;
import org.freshmarker.core.model.TemplateObject;
import org.freshmarker.core.model.primitive.TemplateString;
import org.freshmarker.api.TemplateObjectProvider;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.List;
import java.util.function.Function;
public class FileAndPathExtension implements BuiltInProvider, TemplateObjectProviders {
private static final String SHA_1 = "SHA-1";
private TemplateObject processFile(TemplateObject value, Function<File, ?> function, ProcessContext context) {
File input = ((TemplateFile) value).getValue();
return context.mapObject(function.apply(input));
}
private TemplateObject processPath(TemplateObject value, Function<Path, ?> function, ProcessContext context) {
Path input = ((TemplatePath) value).getValue();
return context.mapObject(PathOperations.execute(input, "inspect", function::apply));
}
private TemplateObject pathOperation(TemplateObject value, String name,
PathOperations.Operation<?> operation, ProcessContext context) {
Path path = value instanceof TemplatePath input ? input.getValue() : ((TemplateFile) value).getValue().toPath();
return context.mapObject(PathOperations.execute(path, name, operation));
}
@Override
public Register<Class<? extends TemplateObject>, String, BuiltIn> provideBuiltInRegister() {
BuiltInRegister register = new BuiltInRegister();
register.add(TemplateFile.class, "exists", (x, y, e) -> processFile(x, File::exists, e));
register.add(TemplateFile.class, "is_directory", (x, y, e) -> processFile(x, File::isDirectory, e));
register.add(TemplateFile.class, "is_file", (x, y, e) -> processFile(x, File::isFile, e));
register.add(TemplateFile.class, "can_execute", (x, y, e) -> processFile(x, File::canExecute, e));
register.add(TemplateFile.class, "can_read", (x, y, e) -> processFile(x, File::canRead, e));
register.add(TemplateFile.class, "can_write", (x, y, e) -> processFile(x, File::canWrite, e));
register.add(TemplateFile.class, "size", (x, y, e) -> processFile(x, File::length, e));
register.add(TemplateFile.class, "length", (x, y, e) -> processFile(x, PathOperations::length, e));
register.add(TemplateFile.class, "name", (x, y, e) -> processFile(x, File::getName, e));
register.add(TemplateFile.class, "parent", (x, y, e) -> processFile(x, File::getParentFile, e));
register.add(TemplatePath.class, "exists", (x, y, e) -> processPath(x, Files::exists, e));
register.add(TemplatePath.class, "is_directory", (x, y, e) -> processPath(x, Files::isDirectory, e));
register.add(TemplatePath.class, "is_file", (x, y, e) -> processPath(x, Files::isRegularFile, e));
register.add(TemplatePath.class, "can_execute", (x, y, e) -> processPath(x, Files::isExecutable, e));
register.add(TemplatePath.class, "can_read", (x, y, e) -> processPath(x, Files::isReadable, e));
register.add(TemplatePath.class, "can_write", (x, y, e) -> processPath(x, Files::isWritable, e));
register.add(TemplatePath.class, "size", (x, y, e) -> processPath(x, PathOperations::size, e));
register.add(TemplatePath.class, "length", (x, y, e) -> processPath(x, PathOperations::length, e));
register.add(TemplatePath.class, "name", (x, y, e) -> processPath(x, PathOperations::name, e));
register.add(TemplatePath.class, "parent", (x, y, e) -> processPath(x, Path::getParent, e));
register.add(TemplatePath.class, "md5", (x, y, e) -> checksum((TemplatePath)x, "MD5"));
register.add(TemplatePath.class, "sha", (x, y, e) -> checksum((TemplatePath)x, SHA_1));
register.add(TemplatePath.class, "sha1", (x, y, e) -> checksum((TemplatePath)x, SHA_1));
register.add(TemplatePath.class, "sha256", (x, y, e) -> checksum((TemplatePath)x, "SHA-256"));
register.add(TemplatePath.class, "sha384", (x, y, e) -> checksum((TemplatePath)x, "SHA-384"));
register.add(TemplatePath.class, "sha512", (x, y, e) -> checksum((TemplatePath)x, "SHA-512"));
register.add(TemplateFile.class, "md5", (x, y, e) -> checksum((TemplateFile)x, "MD5"));
register.add(TemplateFile.class, "sha", (x, y, e) -> checksum((TemplateFile)x, SHA_1));
register.add(TemplateFile.class, "sha1", (x, y, e) -> checksum((TemplateFile)x, SHA_1));
register.add(TemplateFile.class, "sha256", (x, y, e) -> checksum((TemplateFile)x, "SHA-256"));
register.add(TemplateFile.class, "sha384", (x, y, e) -> checksum((TemplateFile)x, "SHA-384"));
register.add(TemplateFile.class, "sha512", (x, y, e) -> checksum((TemplateFile)x, "SHA-512"));
for (Class<? extends TemplateObject> type : List.of(TemplatePath.class, TemplateFile.class)) {
register.add(type, "is_symbolic_link", (x, y, e) -> pathOperation(x, "inspect link", Files::isSymbolicLink, e));
register.add(type, "exists_no_follow", (x, y, e) -> pathOperation(x, "check existence of",
path -> Files.exists(path, LinkOption.NOFOLLOW_LINKS), e));
register.add(type, "link_target", (x, y, e) -> pathOperation(x, "read link target of", Files::readSymbolicLink, e));
register.add(type, "real_path", (x, y, e) -> pathOperation(x, "resolve", Path::toRealPath, e));
register.add(type, "children", (x, y, e) -> pathOperation(x, "list children of", PathOperations::children, e));
}
return register;
}
private TemplateObject checksum(TemplatePath x, String algorithm) {
return checksum(algorithm, x.getValue());
}
private TemplateObject checksum(TemplateFile x, String algorithm) {
return checksum(algorithm, x.getValue().toPath());
}
private static TemplateString checksum(String algorithm, Path value) {
return PathOperations.execute(value, "calculate " + algorithm + " digest of", path -> checksumContent(algorithm, path));
}
private static TemplateString checksumContent(String algorithm, Path value) throws IOException {
try (SeekableByteChannel ch = Files.newByteChannel(value, StandardOpenOption.READ)) {
MessageDigest md = MessageDigest.getInstance(algorithm);
ByteBuffer bf = ByteBuffer.allocate(1000);
while (ch.read(bf) != -1) {
bf.flip();
md.update(bf);
bf.clear();
}
byte[] digest = md.digest();
return new TemplateString(HexFormat.of().formatHex(digest));
} catch (NoSuchAlgorithmException ex) {
throw new ProcessException("unsupported digest algorithm: " + algorithm, ex);
}
}
@Override
public List<TemplateObjectProvider> provideProviders() {
return List.of(
(environment, object) -> object instanceof Path path ? new TemplatePath(path) : null,
(environment, object) -> object instanceof File file ? new TemplateFile(file) : null);
}
}