org.nuiton.jaxx.compiler.binding.DataBindingHelper Maven / Gradle / Ivy
/*
* #%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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.nuiton.jaxx.compiler.CompilerException;
import org.nuiton.jaxx.compiler.I18nHelper;
import org.nuiton.jaxx.compiler.JAXXCompiler;
/**
* Helper to be used by compiler to treate data bindings.
*
* Note : The code in this class was previously directly in JAXXCompiler, now prefer have a separate
* class to make {@link JAXXCompiler} more simple and clear.
*
* Created: 27 nov. 2009
*
* @author Tony Chemit - [email protected]
* @since 2.0.0
*/
public class DataBindingHelper {
/** To debug binding without any log interference */
public static boolean SHOW_LOG;
/** left brace matcher */
protected static final Matcher leftBraceMatcher = Pattern.compile("^(\\{)|[^\\\\](\\{)").matcher("");
/** right brace matcher */
protected static final Matcher rightBraceMatcher = Pattern.compile("^(\\})|[^\\\\](\\})").matcher("");
/**
* Registred data binding for the compiler, then after the invocation of method {@link #finalizeBindings()}
* only the real data bindings, the simple bindings will be moved to {@link #simpleBindings}.
*/
protected final List dataBindings = new ArrayList<>();
/** Simpel bindings for the compiler */
protected final List simpleBindings = new ArrayList<>();
/** Associated compiler */
protected final JAXXCompiler compiler;
/** Counter by unsafe type */
protected final Map autoUnsafeGenIds = new TreeMap<>();
public DataBindingHelper(JAXXCompiler compiler) {
this.compiler = compiler;
}
/**
* Examine an attribute value for data binding expressions. Returns a 'cooked' expression which
* can be used to determine the resulting value. It is expected that this expression will be used
* as the source expression in a call to {@link #registerDataBinding}.
* If the attribute value does not invoke data binding, this method returns null
*
* @param stringValue the string value of the property from the XML
* @return a processed version of the expression
* @throws CompilerException ?
*/
public String processDataBindings(String stringValue) throws CompilerException {
I18nHelper.tryToRegisterI18nInvocation(compiler,stringValue);
int pos = getNextLeftBrace(stringValue, 0);
if (pos != -1) {
StringBuilder expression = new StringBuilder();
int lastPos = 0;
while (pos != -1 && pos < stringValue.length()) {
if (pos > lastPos) {
if (expression.length() > 0) {
expression.append(" + ");
}
expression.append('"');
expression.append(JAXXCompiler.escapeJavaString(stringValue.substring(lastPos, pos)));
expression.append('"');
}
boolean multi = expression.length() > 0;
if (multi) {
expression.append(" + ");
expression.append('(');
}
int pos2 = getNextRightBrace(stringValue, pos + 1);
if (pos2 == -1) {
throw new CompilerException("unmatched '{' in expression: " + stringValue);
}
expression.append(stringValue.substring(pos + 1, pos2));
if (multi) {
expression.append(')');
}
pos2++;
if (pos2 < stringValue.length()) {
pos = getNextLeftBrace(stringValue, pos2);
lastPos = pos2;
} else {
pos = stringValue.length();
lastPos = pos;
}
}
if (lastPos < stringValue.length()) {
if (expression.length() > 0) {
expression.append(" + ");
}
expression.append('"');
expression.append(JAXXCompiler.escapeJavaString(stringValue.substring(lastPos)));
expression.append('"');
}
//TC-20091027 : developper must write exact databinding
// the fact of adding the String boxed for String type binding is not
// a good thing, since it add one more call to process in binding
// and add nothing special more ?
// return type == ClassDescriptorHelper.getClassDescriptor(String.class) ? "String.valueOf(" + expression + ")" : expression.toString();
return expression.toString();
}
return null;
}
public DataBinding[] getDataBindings() {
return dataBindings.toArray(new DataBinding[dataBindings.size()]);
}
public DataBinding[] getSimpleBindings() {
return simpleBindings.toArray(new DataBinding[simpleBindings.size()]);
}
public void registerDataBinding(String id, String binding, String assignment) {
binding = compiler.checkJavaCode(binding);
registerDataBinding(new DataBinding(id, binding, assignment, true));
}
public void registerDataBinding(DataBinding binding) {
dataBindings.add(binding);
}
public void clear() {
simpleBindings.clear();
dataBindings.clear();
autoUnsafeGenIds.clear();
}
/**
* Obtain the next safe id for the given binding id.
*
* With css, we can obtain the same binding id, so we must
* check for unicity each time we want a new binding id.
*
* If an id is already taken, we suffix by {@code _XXX} until
* found a free id.
*
* @param id the id of the binding
* @return the safe id of the binding
*/
public String getSafeId(String id) {
Integer integer = autoUnsafeGenIds.get(id);
String result = id;
if (integer == null) {
integer = 0;
} else {
result += "_" + integer;
}
autoUnsafeGenIds.put(id, ++integer);
return result;
}
/**
* Revert a previous computed safe id.
*
* This is needed when a binding compiled is not an data binding, we want to free
* the safe id to avoid hole in numbers.
*
* @param id the original id to revert in counter.
*/
public void revertSafeId(String id) {
Integer integer = autoUnsafeGenIds.get(id);
if (integer != null) {
integer--;
if (integer > 0) {
autoUnsafeGenIds.put(id, integer);
} else {
autoUnsafeGenIds.remove(id);
}
}
}
/**
* Compile all binding discovered previously.
*
* If a binding is not a dataBinding, then move it from the list {@link #dataBindings} to {@link #simpleBindings}.
*/
public void finalizeBindings() {
for (Iterator itr = dataBindings.iterator(); itr.hasNext(); ) {
DataBinding binding = itr.next();
boolean isBinding = binding.compile(compiler);
if (!isBinding) {
// ce n'est pas un binding, on enregistre le code d'init (si il existe)
simpleBindings.add(binding);
// on supprime le faux binding
itr.remove();
}
}
}
protected static int getNextLeftBrace(String string, int pos) {
leftBraceMatcher.reset(string);
return leftBraceMatcher.find(pos) ? Math.max(leftBraceMatcher.start(1), leftBraceMatcher.start(2)) : -1;
}
protected static int getNextRightBrace(String string, int pos) {
leftBraceMatcher.reset(string);
rightBraceMatcher.reset(string);
int openCount = 1;
int rightPos;
while (openCount > 0) {
pos++;
int leftPos = leftBraceMatcher.find(pos) ?
Math.max(leftBraceMatcher.start(1), leftBraceMatcher.start(2)) :
-1;
rightPos = rightBraceMatcher.find(pos) ?
Math.max(rightBraceMatcher.start(1), rightBraceMatcher.start(2)) :
-1;
assert leftPos == -1 || leftPos >= pos;
assert rightPos == -1 || rightPos >= pos;
if (leftPos != -1 && leftPos < rightPos) {
pos = leftPos;
openCount++;
} else if (rightPos != -1) {
pos = rightPos;
openCount--;
} else {
openCount = 0;
}
}
return pos;
}
}