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

org.nuiton.jaxx.compiler.binding.DataBinding Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * JAXX :: Compiler
 * %%
 * Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.jaxx.compiler.binding;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.compiler.CompiledObject;
import org.nuiton.jaxx.compiler.CompilerException;
import org.nuiton.jaxx.compiler.JAXXCompiler;
import org.nuiton.jaxx.compiler.java.JavaFileGenerator;
import org.nuiton.jaxx.compiler.java.JavaMethod;
import org.nuiton.jaxx.compiler.types.TypeManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Represents a data binding in a JAXX file.  DataBinding uses
 * {@link DataSource} to track changes to a source expression and update
 * the destination.
 */
public class DataBinding {

    /** Logger. */
    protected static final Logger log = LogManager.getLogger(DataBinding.class);

    /** Id of the data binding */
    private final String id;

    /** Real Id of the data binding (can be suffix by a number for css binding to avoid collisions) */
    private String realId;

    /** Constant id build from the {@link #realId} and used instead of {@link #realId} in generated code */
    protected String constantId;

    /** source of the data binding */
    private final String source;

    /**
     * A Java snippet which will cause the destination property to be updated with the current value of
     * the binding.
     */
    private final String assignment;

    /** A internal flag to */
    private final boolean quickNoDependencies;

    /** Compiled data source */
    protected DataSource dataSource;

    /** code to add to processDataBinding (null if no binding) */
    protected String processDataBinding;

    /** code to register the databinding (null if no binding) */
    protected String initDataBinding;

    /** Extra method to add to the binding */
    protected final List methods = new ArrayList<>();

    /** internal state passed to {@code true} when {@link #compile(JAXXCompiler)}  method is invoked */
    private boolean compiled;

    /**
     * Creates a new data binding.
     *
     * @param id                  the data binding destination in the form id.propertyName
     * @param source              the Java source code for the data binding expression
     * @param assignment          Java snippet which will cause the destination property to be updated with the current value of the binding
     * @param quickNoDependencies internal flag to not treate process databinding in not a real binding
     */
    public DataBinding(String id, String source, String assignment, boolean quickNoDependencies) {
        this.id = id;
        this.source = source;
        this.assignment = assignment;
        this.quickNoDependencies = quickNoDependencies;
        if (log.isDebugEnabled()) {
            log.debug("id=" + id + " assignement=" + assignment + " source=" + source + " quickNoDependencies=" + quickNoDependencies);
        }
    }

    public String getAssignment() {
        return assignment;
    }

    public String getSource() {
        return source;
    }

    public boolean isQuickNoDependencies() {
        return quickNoDependencies;
    }

    public String getProcessDataBinding() {
        return processDataBinding;
    }

    public String getInitDataBinding() {
        return initDataBinding;
    }

    public DataListener[] getTrackers() {
        return dataSource == null ? null : dataSource.getTrackers();
    }

    public String getRealId() {
        return realId;
    }

    public String getConstantId() {
        return constantId;
    }

    public List getMethods() {
        return methods;
    }

    @Override
    public String toString() {
        ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
        b.append("id", id);
        b.append("source", source.trim());
        b.append("assignement", assignment.trim());
        b.append("quickNoDependencies", quickNoDependencies);
        if (compiled) {
            b.append("realdId", realId);
            b.append("constantId", getConstantId());
            b.append("objectCode", dataSource.getObjectCode());
            DataListener[] trackers = dataSource.getTrackers();
            if (trackers.length > 0) {
                b.append("source:trackers", trackers.length);
                for (DataListener d : trackers) {
                    b.append("source:tracker", d);
                }
            }
        }
        return b.toString();
    }

    /**
     * Compiles the data binding expression.  This method calls methods in
     * JAXXCompiler to add the Java code that performs the data
     * binding setup.
     *
     * @param compiler compiler which includes the data binding
     * @return {@code true} if the expression has dependencies, {@code false} otherwise
     * @throws CompilerException if a compilation error occurs
     */
    public boolean compile(JAXXCompiler compiler) throws CompilerException {

        if (compiled) {
            throw new IllegalStateException(this + " has already been compiled");
        }

        DataBindingHelper bindingHelper = compiler.getBindingHelper();

        // obtain a safe id
        realId = bindingHelper.getSafeId(id.trim());

        // compute the constant id of the binding
        constantId = TypeManager.convertVariableNameToConstantName("binding_" + realId);

        dataSource = new DataSource(realId, constantId, getSource(), compiler, methods);

        // compile binding

        boolean binding = dataSource.compile();

        if (!binding) {

            // free the generated id

            bindingHelper.revertSafeId(id.trim());
        }

        // was compiled
        compiled = true;

        if (dataSource.showLog()) {
            if (binding) {
                log.info("detect a databinding : " + this);
            } else {
                log.info("reject a databinding : " + getSource());
            }
        }

        // compute initDataBinding code

        initDataBinding = getInitDataBindingCode(compiler, dataSource, binding);

        // compute processDataBinding code

        processDataBinding = getProcessDataBindingCode(compiler, dataSource, binding);

        Set ids = dataSource.getOverrideIds();

        if (binding && ids != null && !ids.isEmpty()) {

            // there is some overrides, check trackers
            DataListener[] listeners = dataSource.getTrackers();
            for (DataListener listener : listeners) {
                String code = listener.getAddListenerCode();
                String newCode = replaceOverrides(compiler, ids, code);
                if (code.equals(newCode)) {
                    listener.addListenerCode = newCode;
                    if (dataSource.showLog()) {
                        log.info("Replace overrides [" + code + "] --> [" + newCode + "]");
                    }
                }
                code = listener.getRemoveListenerCode();
                newCode = replaceOverrides(compiler, ids, code);
                if (code.equals(newCode)) {
                    listener.removeListenerCode = newCode;
                    if (dataSource.showLog()) {
                        log.info("Replace overrides [" + code + "] --> [" + newCode + "]");
                    }
                }
            }
        }
        return binding;
    }

    protected String getInitDataBindingCode(JAXXCompiler compiler, DataSource dataSource, boolean isBinding) {
        String eol = JAXXCompiler.getLineSeparator();
        if (isBinding) {
            return null;
        }
        if (isQuickNoDependencies()) {
            // layout is specially handled early in the chain
            if (!id.endsWith(".layout")) {
                return getAssignment() + eol;
            }
        }
        return null;
    }

    protected String getProcessDataBindingCode(JAXXCompiler compiler, DataSource dataSource, boolean isBinding) {
        if (!isBinding) {
            // no binding = no process code
            return null;
        }
        String eol = JAXXCompiler.getLineSeparator();
        StringBuilder buffer = new StringBuilder();

        String objectCode = dataSource.getObjectCode();

        Set overrideIds = dataSource.getOverrideIds();
        // no need to test objectCode not null if on root object
        boolean needTest = objectCode != null && !objectCode.trim().isEmpty() && !compiler.getRootObject().getId().equals(objectCode + " != null");
        if (needTest) {

            objectCode = replaceOverrides(compiler, overrideIds, objectCode);

            buffer.append("if (").append(objectCode).append(") {").append(eol);
        }
        String assignment = getAssignment(compiler, overrideIds);
        buffer.append(JavaFileGenerator.indent(assignment, needTest ? 4 : 0, false, eol));
        if (needTest) {
            buffer.append(eol).append("}");
        }
        return buffer.toString();
    }

    protected String getAssignment(JAXXCompiler compiler, Set overrides) {
        String s = getAssignment();

        s = replaceOverrides(compiler, overrides, s);

        return s;
    }

    protected String replaceOverrides(JAXXCompiler compiler, Set overrides, String code) {
        if (overrides != null && !overrides.isEmpty()) {
            String tmp = code;
            for (String override : overrides) {
                CompiledObject o = compiler.getCompiledObject(override);
                tmp = tmp.replaceAll(override + "\\.", o.getGetterName() + "().");
//                tmp = tmp.replaceFirst(override + ".", o.getJavaCode() + ".");
            }
            if (dataSource.showLog()) {
                log.info("Assignment with overrides [" + code + "] to [" + tmp + "]");
            }
            code = tmp;
        }
        return code;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy