<commit_before>/*
 * Copyright 2020-2020 <NAME>, https://github.com/pponec
 *
 * 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.
 */
package org.ujorm.tools.web.json;

import java.io.Closeable;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ujorm.tools.web.HtmlElement;
import org.ujorm.tools.web.ao.ObjectProvider;
import org.ujorm.tools.xml.config.HtmlConfig;

/**
 * Simple JSON writer for object type of key-value.
 *
 * @author <NAME>
 */
public class JsonBuilder implements Closeable {

    private static final char DOUBLE_QUOTE = JsonWriter.DOUBLE_QUOTE;

    /** An original writer */
    @NotNull
    private final Appendable writer;
    /** JSON writer with character escaping */
    @NotNull
    private final JsonWriter jsonWriter;
    /** HTML config */
    private final HtmlConfig config;
    /** Parameter counter */
    private int paramCounter = 0;

    /** Constructor with a default HTML config */
    protected JsonBuilder(@NotNull final Appendable writer) {
        this(writer, HtmlConfig.ofEmptyElement());
    }

    /** Common constructor */
    protected JsonBuilder(@NotNull final Appendable writer, HtmlConfig config) {
        this.writer = writer;
        this.jsonWriter = new JsonWriter(writer);
        this.config = config;
    }

    /** Write the value for a CSS ID selector
     *
     * @param elementId ID selector
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder writeId(
            @NotNull final CharSequence elementId,
            @Nullable final CharSequence... values) throws IOException {
        writePrefix(SelectorType.ID.prefix, elementId, values);
        return this;
    }


    /** Write the value for a CSS CLASS selector
     *
     * @param elementId ID selector
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder writeClass(
            @NotNull final CharSequence elementId,
            @Nullable final CharSequence... values) throws IOException {
        writePrefix(SelectorType.CLASS.prefix, elementId, values);
        return this;
    }

    /** Write a key-value
     *
     * @param key A JSON key
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder write(
            @NotNull final CharSequence key,
            @Nullable final CharSequence... values) throws IOException {
        writePrefix(SelectorType.INCLUDED.prefix, key, values);
        return this;
    }

    /** Write a key-value with a prefix
     *
     * @param key A JSON key
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder writePrefix(
            @NotNull final String keyPrefix,
            @NotNull final CharSequence key,
            @Nullable final CharSequence... values) throws IOException {
        writeKey(keyPrefix, key);
        if (values == null) {
            writer.append("null");
        } else {
            writer.append(DOUBLE_QUOTE);
            for (CharSequence value : values) {
                jsonWriter.append(value);
            }
        }
        writer.append(DOUBLE_QUOTE);
        return this;
    }
    
    /** Write a JSON property */
    private void writeKey(final String keyPrefix, final CharSequence key) throws IOException {
        writer.append(paramCounter++ == 0 ? '{' : ',');
        writer.append(DOUBLE_QUOTE);
        jsonWriter.append(keyPrefix);
        jsonWriter.append(key);
        writer.append(DOUBLE_QUOTE);
        writer.append(':');
    }

    // --- VALUE PROVIDER ---


    /** Write the value for a CSS ID selector
     *
     * @param elementId ID selector
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeId(
            @NotNull final CharSequence elementId,
            @NotNull final ValueProvider valueProvider) throws IOException {
        return write(SelectorType.ID.prefix, elementId, valueProvider);
    }


    /** Write the value for a CSS CLASS selector
     *
     * @param elementId ID selector
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeClass(
            @NotNull final CharSequence elementId,
            @NotNull final ValueProvider valueProvider) throws IOException {
        return write(SelectorType.CLASS.prefix, elementId, valueProvider);
    }

    /** Write a key-value
     *
     * @param key A JSON key
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder write(
            @NotNull final CharSequence key,
            @NotNull final ValueProvider valueProvider) throws IOException {
        return write(SelectorType.INCLUDED.prefix, key, valueProvider);
    }

    /**
     *
     * @param keyPrefix Key Prefix
     * @param key Main Key
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder write(
            @NotNull final String keyPrefix,
            @NotNull final CharSequence key,
            @NotNull final ValueProvider valueProvider)
            throws IOException {

        writeKey(keyPrefix, key);
        writer.append(DOUBLE_QUOTE);
        try (HtmlElement root = HtmlElement.of(config, jsonWriter)) {
            valueProvider.accept(root.original());
        }
        writer.append(DOUBLE_QUOTE);
        return this;
    }
    
    @Override
    public void close() throws IOException {
        if (paramCounter == 0) {
            writer.append('{');
        }
        writer.append('}');
    }
    
    // --- OBJECT PROVIER ---

    /** An experimental feature: write the value for a CSS ID selector
     *
     * @param elementId ID selector
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeIdObj(
            @NotNull final CharSequence elementId,
            @NotNull final ObjectProvider objectProvider) throws IOException {
        return writeObj(SelectorType.ID.prefix, elementId, objectProvider);
    }


    /** An experimental feature: write the value for a CSS CLASS selector
     *
     * @param elementId ID selector
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeClassObj(
            @NotNull final CharSequence elementId,
            @NotNull final ObjectProvider objectProvider) throws IOException {
        return writeObj(SelectorType.CLASS.prefix, elementId, objectProvider);
    }

    /** An experimental feature: write a key-object value
     *
     * @param key A JSON key
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeObj(
            @NotNull final CharSequence key,
            @NotNull final ObjectProvider objectProvider) throws IOException {
        return writeObj(SelectorType.INCLUDED.prefix, key, objectProvider);
    }

    /**
     * An experimental feature: write key-object value
     * 
     * @param keyPrefix Key Prefix
     * @param key Main Key
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeObj(
            @NotNull final String keyPrefix,
            @NotNull final CharSequence key,
            @NotNull final ObjectProvider objectProvider)
            throws IOException {

        writeKey(keyPrefix, key);
        objectProvider.accept(this);
        writer.append(DOUBLE_QUOTE);
        return this;
    }
    
    // --- UTILS ---

    /** An object factory */
    @NotNull
    public static final JsonBuilder of(@NotNull final Appendable writer) {
        return new JsonBuilder(writer);
    }

    /** An object factory */
    @NotNull
    public static final JsonBuilder of(
            @NotNull final HttpServletRequest request,
            @NotNull final HttpServletResponse response) throws IllegalStateException, IOException {
        return of(HtmlConfig.ofEmptyElement(), request, response);
    }

    /** An object factory */
    @NotNull
    public static final JsonBuilder of(
            @NotNull final HtmlConfig config,
            @NotNull final HttpServletResponse response) 
            throws IllegalStateException, IOException {
        return of(null, response, config);
    }
    
    /** An object factory */
    @Deprecated
    @NotNull
    public static final JsonBuilder of(
            @Nullable final HttpServletRequest request,
            @NotNull final HttpServletResponse response,
            @NotNull final HtmlConfig config) throws IllegalStateException, IOException {
        return of(config, request, response);
    }
    
    /** An object factory */
    @NotNull
    public static final JsonBuilder of(
            @NotNull final HtmlConfig config,
            @Nullable final HttpServletRequest request,
            @NotNull final HttpServletResponse response) 
            throws IllegalStateException, IOException {
        if (config.isHtmlHeaderRequest()) {
            response.addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1
            response.addHeader("Pragma", "no-cache"); // HTTP 1.0
            response.addHeader("Expires", "0"); // Proxies
        }
        final String charset = config.getCharset().toString();
        response.setCharacterEncoding(charset);
        if (request != null) {
            request.setCharacterEncoding(charset);
        }
        return new JsonBuilder(response.getWriter(), config);
    }

    /** CSS selector types */
    public enum SelectorType {
        /** CSS selector by ID */
        ID("#"),
        /** CSS selector by CLASS */
        CLASS("."),
        /** CSS selector is included */
        INCLUDED("");

        final String prefix;

        private SelectorType(@NotNull String prefix) {
            this.prefix = prefix;
        }

        @NotNull
        public String getPrefix() {
            return prefix;
        }
    }
}
<commit_msg>AJAX supports an injected javascript process.<commit_after>/*
 * Copyright 2020-2020 <NAME>, https://github.com/pponec
 *
 * 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.
 */
package org.ujorm.tools.web.json;

import java.io.Closeable;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ujorm.tools.web.HtmlElement;
import org.ujorm.tools.web.ao.ObjectProvider;
import org.ujorm.tools.xml.config.HtmlConfig;

/**
 * Simple JSON writer for object type of key-value.
 *
 * @author <NAME>
 */
public class JsonBuilder implements Closeable {

    private static final char DOUBLE_QUOTE = JsonWriter.DOUBLE_QUOTE;

    /** An original writer */
    @NotNull
    private final Appendable writer;
    /** JSON writer with character escaping */
    @NotNull
    private final JsonWriter jsonWriter;
    /** HTML config */
    private final HtmlConfig config;
    /** Parameter counter */
    private int paramCounter = 0;
    /** Dummy selector to run a JavaScript */
    private final String JAVACRIPT_DUMMY_SELECTOR = "";

    /** Constructor with a default HTML config */
    protected JsonBuilder(@NotNull final Appendable writer) {
        this(writer, HtmlConfig.ofEmptyElement());
    }

    /** Common constructor */
    protected JsonBuilder(@NotNull final Appendable writer, HtmlConfig config) {
        this.writer = writer;
        this.jsonWriter = new JsonWriter(writer);
        this.config = config;
    }

    /** Write the value for a CSS ID selector
     *
     * @param elementId ID selector
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder writeId(
            @NotNull final CharSequence elementId,
            @Nullable final CharSequence... values) throws IOException {
        writePrefix(SelectorType.ID.prefix, elementId, values);
        return this;
    }


    /** Write the value for a CSS CLASS selector
     *
     * @param elementId ID selector
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder writeClass(
            @NotNull final CharSequence elementId,
            @Nullable final CharSequence... values) throws IOException {
        writePrefix(SelectorType.CLASS.prefix, elementId, values);
        return this;
    }

    /** Write a key-value
     *
     * @param key A JSON key
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder write(
            @NotNull final CharSequence key,
            @Nullable final CharSequence... values) throws IOException {
        writePrefix(SelectorType.INCLUDED.prefix, key, values);
        return this;
    }

    /** Write a key-value with a prefix
     *
     * @param key A JSON key
     * @param values The text array to join.
     * @throws IOException
     */
    public JsonBuilder writePrefix(
            @NotNull final String keyPrefix,
            @NotNull final CharSequence key,
            @Nullable final CharSequence... values) throws IOException {
        writeKey(keyPrefix, key);
        if (values == null) {
            writer.append("null");
        } else {
            writer.append(DOUBLE_QUOTE);
            for (CharSequence value : values) {
                jsonWriter.append(value);
            }
        }
        writer.append(DOUBLE_QUOTE);
        return this;
    }
    
    /** Write a Javascript to a call. 
     * The response can contain only one Javascript code, 
     * so this method can be called only once per request.
     */
    public JsonBuilder writeJs(@Nullable final CharSequence... javascript) throws IOException {
        return write(JAVACRIPT_DUMMY_SELECTOR, javascript);
    }
    
    /** Write a JSON property */
    private void writeKey(final String keyPrefix, final CharSequence key) throws IOException {
        writer.append(paramCounter++ == 0 ? '{' : ',');
        writer.append(DOUBLE_QUOTE);
        jsonWriter.append(keyPrefix);
        jsonWriter.append(key);
        writer.append(DOUBLE_QUOTE);
        writer.append(':');
    }

    // --- VALUE PROVIDER ---


    /** Write the value for a CSS ID selector
     *
     * @param elementId ID selector
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeId(
            @NotNull final CharSequence elementId,
            @NotNull final ValueProvider valueProvider) throws IOException {
        return write(SelectorType.ID.prefix, elementId, valueProvider);
    }


    /** Write the value for a CSS CLASS selector
     *
     * @param elementId ID selector
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeClass(
            @NotNull final CharSequence elementId,
            @NotNull final ValueProvider valueProvider) throws IOException {
        return write(SelectorType.CLASS.prefix, elementId, valueProvider);
    }

    /** Write a key-value
     *
     * @param key A JSON key
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder write(
            @NotNull final CharSequence key,
            @NotNull final ValueProvider valueProvider) throws IOException {
        return write(SelectorType.INCLUDED.prefix, key, valueProvider);
    }

    /**
     *
     * @param keyPrefix Key Prefix
     * @param key Main Key
     * @param valueProvider A value provider
     * @throws IOException
     */
    public JsonBuilder write(
            @NotNull final String keyPrefix,
            @NotNull final CharSequence key,
            @NotNull final ValueProvider valueProvider)
            throws IOException {

        writeKey(keyPrefix, key);
        writer.append(DOUBLE_QUOTE);
        try (HtmlElement root = HtmlElement.of(config, jsonWriter)) {
            valueProvider.accept(root.original());
        }
        writer.append(DOUBLE_QUOTE);
        return this;
    }
    
    @Override
    public void close() throws IOException {
        if (paramCounter == 0) {
            writer.append('{');
        }
        writer.append('}');
    }
    
    // --- OBJECT PROVIER ---

    /** An experimental feature: write the value for a CSS ID selector
     *
     * @param elementId ID selector
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeIdObj(
            @NotNull final CharSequence elementId,
            @NotNull final ObjectProvider objectProvider) throws IOException {
        return writeObj(SelectorType.ID.prefix, elementId, objectProvider);
    }


    /** An experimental feature: write the value for a CSS CLASS selector
     *
     * @param elementId ID selector
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeClassObj(
            @NotNull final CharSequence elementId,
            @NotNull final ObjectProvider objectProvider) throws IOException {
        return writeObj(SelectorType.CLASS.prefix, elementId, objectProvider);
    }

    /** An experimental feature: write a key-object value
     *
     * @param key A JSON key
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeObj(
            @NotNull final CharSequence key,
            @NotNull final ObjectProvider objectProvider) throws IOException {
        return writeObj(SelectorType.INCLUDED.prefix, key, objectProvider);
    }

    /**
     * An experimental feature: write key-object value
     * 
     * @param keyPrefix Key Prefix
     * @param key Main Key
     * @param objectProvider A value provider
     * @throws IOException
     */
    public JsonBuilder writeObj(
            @NotNull final String keyPrefix,
            @NotNull final CharSequence key,
            @NotNull final ObjectProvider objectProvider)
            throws IOException {

        writeKey(keyPrefix, key);
        objectProvider.accept(this);
        writer.append(DOUBLE_QUOTE);
        return this;
    }
    
    // --- UTILS ---

    /** An object factory */
    @NotNull
    public static final JsonBuilder of(@NotNull final Appendable writer) {
        return new JsonBuilder(writer);
    }

    /** An object factory */
    @NotNull
    public static final JsonBuilder of(
            @NotNull final HttpServletRequest request,
            @NotNull final HttpServletResponse response) throws IllegalStateException, IOException {
        return of(HtmlConfig.ofEmptyElement(), request, response);
    }

    /** An object factory */
    @NotNull
    public static final JsonBuilder of(
            @NotNull final HtmlConfig config,
            @NotNull final HttpServletResponse response) 
            throws IllegalStateException, IOException {
        return of(null, response, config);
    }
    
    /** An object factory */
    @Deprecated
    @NotNull
    public static final JsonBuilder of(
            @Nullable final HttpServletRequest request,
            @NotNull final HttpServletResponse response,
            @NotNull final HtmlConfig config) throws IllegalStateException, IOException {
        return of(config, request, response);
    }
    
    /** An object factory */
    @NotNull
    public static final JsonBuilder of(
            @NotNull final HtmlConfig config,
            @Nullable final HttpServletRequest request,
            @NotNull final HttpServletResponse response) 
            throws IllegalStateException, IOException {
        if (config.isHtmlHeaderRequest()) {
            response.addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1
            response.addHeader("Pragma", "no-cache"); // HTTP 1.0
            response.addHeader("Expires", "0"); // Proxies
        }
        final String charset = config.getCharset().toString();
        response.setCharacterEncoding(charset);
        if (request != null) {
            request.setCharacterEncoding(charset);
        }
        return new JsonBuilder(response.getWriter(), config);
    }

    /** CSS selector types */
    public enum SelectorType {
        /** CSS selector by ID */
        ID("#"),
        /** CSS selector by CLASS */
        CLASS("."),
        /** CSS selector is included */
        INCLUDED("");

        final String prefix;

        private SelectorType(@NotNull String prefix) {
            this.prefix = prefix;
        }

        @NotNull
        public String getPrefix() {
            return prefix;
        }
    }
}

{'fraction_non_alphanumeric': 0.08525493798805696, 'fraction_numerical': 0.0018373909049150207, 'mean_word_length': 2.3111787072243346, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1795147', 'n_tokens_mistral': 5425, 'n_tokens_neox': 5300, 'n_words': 1794}