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;
}
}