All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.composum.sling.core.util.ValueEmbeddingWriter Maven / Gradle / Ivy

package com.composum.sling.core.util;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.FilterWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Formatter;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Stack;

import static com.composum.sling.core.util.ValueEmbeddingReader.TYPE_RESOURCE;

/**
 * a filter writer implementation to embed values from a value map for written placeholders
 */
public class ValueEmbeddingWriter extends FilterWriter {

    interface TokenWriter {

        boolean write(char token) throws IOException;

        void flush() throws IOException;
    }

    protected class TextWriter implements TokenWriter {

        protected boolean escape = false;

        public boolean write(char token) throws IOException {
            switch (token) {
                case '\\':
                    if (escape) {
                        pass('\\');
                        escape = false;
                    } else {
                        escape = true;
                    }
                    break;
                case '$':
                    if (escape) {
                        pass(token);
                        escape = false;
                    } else {
                        writerStack.push(new ValueWriter());
                    }
                    break;
                default:
                    if (escape) {
                        pass('\\');
                        escape = false;
                    }
                    pass(token);
                    break;
            }
            return false;
        }

        public void flush() throws IOException {
            if (escape) {
                pass('\\');
            }
        }
    }

    protected class ValueWriter implements TokenWriter {

        protected StringBuilder keyBuffer = null;

        public boolean write(char token) throws IOException {
            switch (token) {
                case '{':
                    if (keyBuffer == null) {
                        keyBuffer = new StringBuilder();
                    } else if (keyBuffer.length() == 0) {
                        pass("${{");
                        return true;
                    }
                    break;
                case '}':
                    if (keyBuffer == null) {
                        pass("$}");
                    } else if (keyBuffer.length() == 0) {
                        pass("${}");
                    } else {
                        final ValueEmbeddingReader.Key key = new ValueEmbeddingReader.Key(keyBuffer.toString().trim());
                        if (TYPE_RESOURCE.equals(key.type)) {
                            if (resourceContext != null) {
                                try (InputStream stream = resourceContext.getResourceAsStream(key.name);
                                     InputStreamReader reader = stream != null
                                             ? new InputStreamReader(stream, StandardCharsets.UTF_8) : null) {
                                    embed(key, reader);
                                }
                            } else if (keepUnresolvable) {
                                pass("${" + key + "}");
                            }
                        } else {
                            Object value = values.get(key.name);
                            if (value == null && resourceBundle != null) {
                                try {
                                    value = resourceBundle.getString(key.name);
                                } catch (MissingResourceException mrex) {
                                    value = key.name;
                                }
                            }
                            if (value instanceof Reader) {
                                embed(key, (Reader) value);
                            } else if (value != null) {
                                String string;
                                if (StringUtils.isNotBlank(key.format)) {
                                    StringBuilder builder = new StringBuilder();
                                    Formatter formatter = new Formatter(builder);
                                    formatter.format(locale, key.format, value);
                                    string = builder.toString();
                                } else {
                                    string = value.toString();
                                }
                                pass(string);
                            } else if (keepUnresolvable) {
                                pass("${" + key + "}");
                            }
                        }
                    }
                    return true;
                default:
                    if (keyBuffer != null) {
                        keyBuffer.append(token);
                    } else {
                        pass("$" + token);
                        return true;
                    }
                    break;
            }
            return false;
        }

        public void flush() throws IOException {
            if (keyBuffer == null) {
                pass("$");
            } else {
                pass("${" + keyBuffer);
            }
        }

        protected void embed(@NotNull final ValueEmbeddingReader.Key key, @Nullable final Reader reader)
                throws IOException {
            if (reader != null) {
                wrappedWriter.flush();
                IOUtils.copy(new ValueEmbeddingReader(reader, values, locale, resourceContext, resourceBundle),
                        wrappedWriter);
            } else if (keepUnresolvable) {
                pass("${" + key + "}");
            }
        }
    }

    protected final Writer wrappedWriter;
    protected final ValueMap values;
    protected final Locale locale;
    protected final Class resourceContext;
    protected final ResourceBundle resourceBundle;
    protected boolean keepUnresolvable = false;

    protected final Stack writerStack = new Stack<>();

    /**
     * Creates a tag filtering reader to filter out the tags configured as sets of tag names
     *
     * @param writer the writer to filter
     * @param values the set of available placeholders
     */
    public ValueEmbeddingWriter(@NotNull final Writer writer, @NotNull Map values) {
        this(writer, values, null, null, null);
    }

    /**
     * Creates a tag filtering reader to filter out the tags configured as sets of tag names
     *
     * @param writer          the writer to filter
     * @param values          the set of available placeholders
     * @param locale          the locale to use for value formatting
     * @param resourceContext the context class for resource loading
     */
    public ValueEmbeddingWriter(@NotNull final Writer writer, @NotNull Map values,
                                @Nullable Locale locale, @Nullable Class resourceContext) {
        this(writer, values, locale, resourceContext, null);
    }

    /**
     * Creates a tag filtering reader to filter out the tags configured as sets of tag names
     *
     * @param writer          the writer to filter
     * @param values          the set of available placeholders
     * @param locale          the locale to use for value formatting
     * @param resourceContext the context class for resource loading
     * @param resourceBundle  the translations bundle (switches translation on)
     */
    public ValueEmbeddingWriter(@NotNull final Writer writer, @NotNull Map values,
                                @Nullable Locale locale, @Nullable Class resourceContext,
                                @Nullable ResourceBundle resourceBundle) {
        super(writer);
        wrappedWriter = writer;
        this.values = values instanceof ValueMap ? ((ValueMap) values) : new ValueMapDecorator(values);
        this.locale = locale != null ? locale : Locale.getDefault();
        this.resourceContext = resourceContext != null ? resourceContext : values.getClass();
        this.resourceBundle = resourceBundle;
        writerStack.push(new TextWriter());
    }

    public boolean isKeepUnresolvable(final boolean... decision) {
        return decision.length > 0 ? (keepUnresolvable = decision[0]) : keepUnresolvable;
    }

    //
    // Writer...
    //

    @Override
    public void flush() throws IOException {
        while (!writerStack.isEmpty()) {
            writerStack.peek().flush();
            writerStack.pop();
        }
        super.flush();
    }

    @Override
    public void write(int token) throws IOException {
        if (writerStack.peek().write((char) token)) {
            writerStack.pop();
        }
    }

    @Override
    public void write(String str, int off, int len) throws IOException {
        write(str.toCharArray(), off, len);
    }

    /**
     * uses the single token read() method to fill the buffer
     */
    @Override
    public void write(char[] cbuf, int off, int len) throws IOException {
        for (int count = 0; count < len; count++) {
            write(cbuf[off + count]);
        }
    }

    //
    // final write
    //

    public void pass(String str) throws IOException {
        wrappedWriter.write(str);
    }

    public void pass(String str, int off, int len) throws IOException {
        wrappedWriter.write(str, off, len);
    }

    public void pass(char[] cbuf, int off, int len) throws IOException {
        wrappedWriter.write(cbuf, off, len);
    }

    public void pass(int chr) throws IOException {
        wrappedWriter.write(chr);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy