/**
* Copyright 2013-2014 Ralph Schaer <ralphschaer@gmail.com>
*
* 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 ch.rasc.extclassgenerator;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.StringUtils;
import ch.rasc.extclassgenerator.association.AbstractAssociation;
import ch.rasc.extclassgenerator.validation.AbstractValidation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Generator for creating ExtJS and Touch Model objects (JS code) based on a provided
* class or {@link ModelBean}.
*/
public abstract class ModelGenerator {
public static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final Map<JsCacheKey, SoftReference<String>> jsCache = new ConcurrentHashMap<JsCacheKey, SoftReference<String>>();
private static final Map<ModelCacheKey, SoftReference<ModelBean>> modelCache = new ConcurrentHashMap<ModelCacheKey, SoftReference<ModelBean>>();
/**
* Instrospects the provided class, creates a model object (JS code) and writes it
* into the response. Creates compressed JS code. Method ignores any validation
* annotations.
*
* @param request the http servlet request
* @param response the http servlet response
* @param clazz class that the generator should introspect
* @param format specifies which code (ExtJS or Touch) the generator should create.
* @throws IOException
*
* @see #writeModel(HttpServletRequest, HttpServletResponse, Class, OutputFormat,
* boolean)
*/
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, Class<?> clazz, OutputFormat format)
throws IOException {
writeModel(request, response, clazz, format, IncludeValidation.NONE, false);
}
/**
* Instrospects the provided class, creates a model object (JS code) and writes it
* into the response. Method ignores any validation annotations.
*
* @param request the http servlet request
* @param response the http servlet response
* @param clazz class that the generator should introspect
* @param format specifies which code (ExtJS or Touch) the generator should create
* @param debug if true the generator creates the output in pretty format, false the
* output is compressed
* @throws IOException
*/
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, Class<?> clazz, OutputFormat format,
boolean debug) throws IOException {
writeModel(request, response, clazz, format, IncludeValidation.NONE, debug);
}
/**
* Instrospects the provided class, creates a model object (JS code) and writes it
* into the response.
*
* @param request the http servlet request
* @param response the http servlet response
* @param clazz class that the generator should introspect
* @param format specifies which code (ExtJS or Touch) the generator should create
* @param includeValidation specifies if any validation configurations should be added
* to the model code
* @param debug if true the generator creates the output in pretty format, false the
* output is compressed
* @throws IOException
*/
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, Class<?> clazz, OutputFormat format,
IncludeValidation includeValidation, boolean debug) throws IOException {
OutputConfig outputConfig = new OutputConfig();
outputConfig.setIncludeValidation(includeValidation);
outputConfig.setOutputFormat(format);
outputConfig.setDebug(debug);
ModelBean model = createModel(clazz, outputConfig);
writeModel(request, response, model, outputConfig);
}
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, Class<?> clazz, OutputConfig outputConfig)
throws IOException {
ModelBean model = createModel(clazz, outputConfig);
writeModel(request, response, model, outputConfig);
}
/**
* Creates a model object (JS code) based on the provided {@link ModelBean} and writes
* it into the response. Creates compressed JS code.
*
* @param request the http servlet request
* @param response the http servlet response
* @param model {@link ModelBean} describing the model to be generated
* @param format specifies which code (ExtJS or Touch) the generator should create.
* @throws IOException
*/
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, ModelBean model, OutputFormat format)
throws IOException {
writeModel(request, response, model, format, false);
}
/**
* Creates a model object (JS code) based on the provided ModelBean and writes it into
* the response.
*
* @param request the http servlet request
* @param response the http servlet response
* @param model {@link ModelBean} describing the model to be generated
* @param format specifies which code (ExtJS or Touch) the generator should create.
* @param debug if true the generator creates the output in pretty format, false the
* output is compressed
* @throws IOException
*/
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, ModelBean model, OutputFormat format,
boolean debug) throws IOException {
OutputConfig outputConfig = new OutputConfig();
outputConfig.setDebug(debug);
outputConfig.setOutputFormat(format);
writeModel(request, response, model, outputConfig);
}
/**
* Instrospects the provided class and creates a {@link ModelBean} instance. A program
* could customize this and call
* {@link #generateJavascript(ModelBean, OutputFormat, boolean)} or
* {@link #writeModel(HttpServletRequest, HttpServletResponse, ModelBean, OutputFormat)}
* to create the JS code. Calling this method does not add any validation
* configuration.
*
* @param clazz the model will be created based on this class.
* @return a instance of {@link ModelBean} that describes the provided class and can
* be used for Javascript generation.
*/
public static ModelBean createModel(Class<?> clazz) {
return createModel(clazz, IncludeValidation.NONE);
}
/**
* Instrospects the provided class and creates a {@link ModelBean} instance. A program
* could customize this and call
* {@link #generateJavascript(ModelBean, OutputFormat, boolean)} or
* {@link #writeModel(HttpServletRequest, HttpServletResponse, ModelBean, OutputFormat)}
* to create the JS code. Models are being cached. A second call with the same
* parameters will return the model from the cache.
*
* @param clazz the model will be created based on this class.
* @param includeValidation specifies what validation configuration should be added
* @return a instance of {@link ModelBean} that describes the provided class and can
* be used for Javascript generation.
*/
public static ModelBean createModel(Class<?> clazz,
IncludeValidation includeValidation) {
OutputConfig outputConfig = new OutputConfig();
outputConfig.setIncludeValidation(includeValidation);
return createModel(clazz, outputConfig);
}
/**
* Instrospects the provided class, creates a model object (JS code) and returns it.
* This method does not add any validation configuration.
*
* @param clazz class that the generator should introspect
* @param format specifies which code (ExtJS or Touch) the generator should create
* @param debug if true the generator creates the output in pretty format, false the
* output is compressed
* @return the generated model object (JS code)
*/
public static String generateJavascript(Class<?> clazz, OutputFormat format,
boolean debug) {
ModelBean model = createModel(clazz, IncludeValidation.NONE);
return generateJavascript(model, format, debug);
}
public static String generateJavascript(Class<?> clazz, OutputConfig outputConfig) {
ModelBean model = createModel(clazz, outputConfig);
return generateJavascript(model, outputConfig);
}
/**
* Instrospects the provided class, creates a model object (JS code) and returns it.
*
* @param clazz class that the generator should introspect
* @param format specifies which code (ExtJS or Touch) the generator should create
* @param includeValidation specifies what validation configuration should be added to
* the mode code
* @param debug if true the generator creates the output in pretty format, false the
* output is compressed
* @return the generated model object (JS code)
*/
public static String generateJavascript(Class<?> clazz, OutputFormat format,
IncludeValidation includeValidation, boolean debug) {
ModelBean model = createModel(clazz, includeValidation);
return generateJavascript(model, format, debug);
}
/**
* Creates JS code based on the provided {@link ModelBean} in the specified
* {@link OutputFormat}. Code can be generated in pretty or compressed format. The
* generated code is cached unless debug is true. A second call to this method with
* the same model name and format will return the code from the cache.
*
* @param model generate code based on this {@link ModelBean}
* @param format specifies which code (ExtJS or Touch) the generator should create
* @param debug if true the generator creates the output in pretty format, false the
* output is compressed
* @return the generated model object (JS code)
*/
public static String generateJavascript(ModelBean model, OutputFormat format,
boolean debug) {
OutputConfig outputConfig = new OutputConfig();
outputConfig.setOutputFormat(format);
outputConfig.setDebug(debug);
return generateJavascript(model, outputConfig);
}
public static void writeModel(HttpServletRequest request,
HttpServletResponse response, ModelBean model, OutputConfig outputConfig)
throws IOException {
byte[] data = generateJavascript(model, outputConfig).getBytes(UTF8_CHARSET);
String ifNoneMatch = request.getHeader("If-None-Match");
String etag = "\"0" + DigestUtils.md5DigestAsHex(data) + "\"";
if (etag.equals(ifNoneMatch)) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
response.setContentType("application/javascript");
response.setCharacterEncoding(UTF8_CHARSET.name());
response.setContentLength(data.length);
response.setHeader("ETag", etag);
@SuppressWarnings("resource")
ServletOutputStream out = response.getOutputStream();
out.write(data);
out.flush();
}
public static ModelBean createModel(final Class<?> clazz,
final OutputConfig outputConfig) {
Assert.notNull(clazz, "clazz must not be null");
Assert.notNull(outputConfig.getIncludeValidation(),
"includeValidation must not be null");
ModelCacheKey key = new ModelCacheKey(clazz.getName(),
outputConfig.getIncludeValidation());
SoftReference<ModelBean> modelReference = modelCache.get(key);
if (modelReference != null && modelReference.get() != null) {
return modelReference.get();
}
Model modelAnnotation = clazz.getAnnotation(Model.class);
final ModelBean model = new ModelBean();
if (modelAnnotation != null && StringUtils.hasText(modelAnnotation.value())) {
model.setName(modelAnnotation.value());
}
else {
model.setName(clazz.getName());
}
if (modelAnnotation != null) {
model.setIdProperty(modelAnnotation.idProperty());
model.setVersionProperty(trimToNull(modelAnnotation.versionProperty()));
model.setPaging(modelAnnotation.paging());
model.setDisablePagingParameters(modelAnnotation.disablePagingParameters());
model.setCreateMethod(trimToNull(modelAnnotation.createMethod()));
model.setReadMethod(trimToNull(modelAnnotation.readMethod()));
model.setUpdateMethod(trimToNull(modelAnnotation.updateMethod()));
model.setDestroyMethod(trimToNull(modelAnnotation.destroyMethod()));
model.setMessageProperty(trimToNull(modelAnnotation.messageProperty()));
model.setWriter(trimToNull(modelAnnotation.writer()));
model.setSuccessProperty(trimToNull(modelAnnotation.successProperty()));
model.setTotalProperty(trimToNull(modelAnnotation.totalProperty()));
model.setRootProperty(trimToNull(modelAnnotation.rootProperty()));
model.setWriteAllFields(modelAnnotation.writeAllFields());
}
final Set<String> hasReadMethod = new HashSet<String>();
BeanInfo bi;
try {
bi = Introspector.getBeanInfo(clazz);
}
catch (IntrospectionException e) {
throw new RuntimeException(e);
}
for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
if (pd.getReadMethod() != null
&& pd.getReadMethod().getAnnotation(JsonIgnore.class) == null) {
hasReadMethod.add(pd.getName());
}
}
if (clazz.isInterface()) {
final List<Method> methods = new ArrayList<Method>();
ReflectionUtils.doWithMethods(clazz, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
methods.add(method);
}
});
Collections.sort(methods, new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (Method method : methods) {
createModelBean(model, method, outputConfig);
}
}
else {
final Set<String> fields = new HashSet<String>();
Set<ModelField> modelFieldsOnType = AnnotationUtils.getRepeatableAnnotation(
clazz, ModelFields.class, ModelField.class);
for (ModelField modelField : modelFieldsOnType) {
if (StringUtils.hasText(modelField.value())) {
ModelFieldBean modelFieldBean;
if (StringUtils.hasText(modelField.customType())) {
modelFieldBean = new ModelFieldBean(modelField.value(),
modelField.customType());
}
else {
modelFieldBean = new ModelFieldBean(modelField.value(),
modelField.type());
}
updateModelFieldBean(modelFieldBean, modelField);
model.addField(modelFieldBean);
}
}
Set<ModelAssociation> modelAssociationsOnType = AnnotationUtils
.getRepeatableAnnotation(clazz, ModelAssociations.class,
ModelAssociation.class);
for (ModelAssociation modelAssociationAnnotation : modelAssociationsOnType) {
AbstractAssociation modelAssociation = AbstractAssociation
.createAssociation(modelAssociationAnnotation);
if (modelAssociation != null) {
model.addAssociation(modelAssociation);
}
}
Set<ModelValidation> modelValidationsOnType = AnnotationUtils
.getRepeatableAnnotation(clazz, ModelValidations.class,
ModelValidation.class);
for (ModelValidation modelValidationAnnotation : modelValidationsOnType) {
AbstractValidation modelValidation = AbstractValidation.createValidation(
modelValidationAnnotation.propertyName(),
modelValidationAnnotation, outputConfig.getIncludeValidation());
if (modelValidation != null) {
model.addValidation(modelValidation);
}
}
ReflectionUtils.doWithFields(clazz, new FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException,
IllegalAccessException {
if (!fields.contains(field.getName())
&& (field.getAnnotation(ModelField.class) != null
|| field.getAnnotation(ModelAssociation.class) != null || (Modifier
.isPublic(field.getModifiers()) || hasReadMethod
.contains(field.getName()))
&& field.getAnnotation(JsonIgnore.class) == null)) {
// ignore superclass declarations of fields already
// found in a subclass
fields.add(field.getName());
createModelBean(model, field, outputConfig);
}
}
});
}
modelCache.put(key, new SoftReference<ModelBean>(model));
return model;
}
private static void createModelBean(ModelBean model,
AccessibleObject accessibleObject, OutputConfig outputConfig) {
Class<?> javaType = null;
String name = null;
Class<?> declaringClass = null;
if (accessibleObject instanceof Field) {
Field field = (Field) accessibleObject;
javaType = field.getType();
name = field.getName();
declaringClass = field.getDeclaringClass();
}
else if (accessibleObject instanceof Method) {
Method method = (Method) accessibleObject;
javaType = method.getReturnType();
if (javaType.equals(Void.TYPE)) {
return;
}
if (method.getName().startsWith("get")) {
name = StringUtils.uncapitalize(method.getName().substring(3));
}
else if (method.getName().startsWith("is")) {
name = StringUtils.uncapitalize(method.getName().substring(2));
}
else {
name = method.getName();
}
declaringClass = method.getDeclaringClass();
}
ModelType modelType = null;
for (ModelType mt : ModelType.values()) {
if (mt.supports(javaType)) {
modelType = mt;
break;
}
}
ModelFieldBean modelFieldBean = null;
ModelField modelFieldAnnotation = accessibleObject
.getAnnotation(ModelField.class);
if (modelFieldAnnotation != null) {
if (StringUtils.hasText(modelFieldAnnotation.value())) {
name = modelFieldAnnotation.value();
}
if (StringUtils.hasText(modelFieldAnnotation.customType())) {
modelFieldBean = new ModelFieldBean(name,
modelFieldAnnotation.customType());
}
else {
ModelType type = null;
if (modelFieldAnnotation.type() != ModelType.AUTO) {
type = modelFieldAnnotation.type();
}
else {
type = modelType;
}
modelFieldBean = new ModelFieldBean(name, type);
}
updateModelFieldBean(modelFieldBean, modelFieldAnnotation);
model.addField(modelFieldBean);
}
else {
if (modelType != null) {
modelFieldBean = new ModelFieldBean(name, modelType);
model.addField(modelFieldBean);
}
}
ModelAssociation modelAssociationAnnotation = accessibleObject
.getAnnotation(ModelAssociation.class);
if (modelAssociationAnnotation != null) {
model.addAssociation(AbstractAssociation.createAssociation(
modelAssociationAnnotation, model, javaType, declaringClass, name));
}
if (modelFieldBean != null
&& outputConfig.getIncludeValidation() != IncludeValidation.NONE) {
Set<ModelValidation> modelValidationAnnotations = AnnotationUtils
.getRepeatableAnnotation(accessibleObject, ModelValidations.class,
ModelValidation.class);
if (!modelValidationAnnotations.isEmpty()) {
for (ModelValidation modelValidationAnnotation : modelValidationAnnotations) {
AbstractValidation modelValidation = AbstractValidation
.createValidation(name, modelValidationAnnotation,
outputConfig.getIncludeValidation());
if (modelValidation != null) {
model.addValidation(modelValidation);
}
}
}
else {
Annotation[] fieldAnnotations = accessibleObject.getAnnotations();
for (Annotation fieldAnnotation : fieldAnnotations) {
AbstractValidation.addValidationToModel(model, modelFieldBean,
fieldAnnotation, outputConfig.getIncludeValidation());
}
if (accessibleObject instanceof Field) {
PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(
declaringClass, name);
if (pd != null && pd.getReadMethod() != null) {
for (Annotation readMethodAnnotation : pd.getReadMethod()
.getAnnotations()) {
AbstractValidation.addValidationToModel(model,
modelFieldBean, readMethodAnnotation,
outputConfig.getIncludeValidation());
}
}
}
}
}
}
private static void updateModelFieldBean(ModelFieldBean modelFieldBean,
ModelField modelFieldAnnotation) {
ModelType type = modelFieldBean.getModelType();
if (StringUtils.hasText(modelFieldAnnotation.dateFormat())
&& type == ModelType.DATE) {
modelFieldBean.setDateFormat(modelFieldAnnotation.dateFormat());
}
String defaultValue = modelFieldAnnotation.defaultValue();
if (StringUtils.hasText(defaultValue)) {
if (ModelField.DEFAULTVALUE_UNDEFINED.equals(defaultValue)) {
modelFieldBean.setDefaultValue(ModelField.DEFAULTVALUE_UNDEFINED);
}
else {
if (type == ModelType.BOOLEAN) {
modelFieldBean.setDefaultValue(Boolean.parseBoolean(defaultValue));
}
else if (type == ModelType.INTEGER) {
modelFieldBean.setDefaultValue(Long.valueOf(defaultValue));
}
else if (type == ModelType.FLOAT) {
modelFieldBean.setDefaultValue(Double.valueOf(defaultValue));
}
else {
modelFieldBean.setDefaultValue("\"" + defaultValue + "\"");
}
}
}
if (modelFieldAnnotation.useNull()
&& (type == ModelType.INTEGER || type == ModelType.FLOAT
|| type == ModelType.STRING || type == ModelType.BOOLEAN)) {
modelFieldBean.setUseNull(true);
}
modelFieldBean.setMapping(trimToNull(modelFieldAnnotation.mapping()));
if (!modelFieldAnnotation.persist()) {
modelFieldBean.setPersist(false);
}
if (modelFieldAnnotation.critical()) {
modelFieldBean.setCritical(true);
}
modelFieldBean.setConvert(trimToNull(modelFieldAnnotation.convert()));
modelFieldBean.setCalculate(trimToNull(modelFieldAnnotation.calculate()));
}
public static String generateJavascript(ModelBean model, OutputConfig config) {
if (!config.isDebug()) {
JsCacheKey key = new JsCacheKey(model, config);
SoftReference<String> jsReference = jsCache.get(key);
if (jsReference != null && jsReference.get() != null) {
return jsReference.get();
}
}
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
if (!config.isSurroundApiWithQuotes()) {
if (config.getOutputFormat() == OutputFormat.EXTJS5) {
mapper.addMixInAnnotations(ProxyObject.class,
ProxyObjectWithoutApiQuotesExtJs5Mixin.class);
}
else {
mapper.addMixInAnnotations(ProxyObject.class,
ProxyObjectWithoutApiQuotesMixin.class);
}
mapper.addMixInAnnotations(ApiObject.class, ApiObjectMixin.class);
}
else {
if (config.getOutputFormat() != OutputFormat.EXTJS5) {
mapper.addMixInAnnotations(ProxyObject.class,
ProxyObjectWithApiQuotesMixin.class);
}
}
Map<String, Object> modelObject = new LinkedHashMap<String, Object>();
modelObject.put("extend", "Ext.data.Model");
if (!model.getAssociations().isEmpty()) {
Set<String> usesClasses = new HashSet<String>();
for (AbstractAssociation association : model.getAssociations()) {
usesClasses.add(association.getModel());
}
usesClasses.remove(model.getName());
if (!usesClasses.isEmpty()) {
modelObject.put("uses", usesClasses);
}
}
Map<String, Object> configObject = new LinkedHashMap<String, Object>();
final Map<String, ModelFieldBean> fields = model.getFields();
if (!model.getValidations().isEmpty()
&& config.getOutputFormat() == OutputFormat.EXTJS5) {
Set<String> requires = addValidatorsToField(fields, model.getValidations());
if (!requires.isEmpty()) {
configObject.put("requires", requires);
}
}
if (StringUtils.hasText(model.getIdProperty())
&& !model.getIdProperty().equals("id")) {
configObject.put("idProperty", model.getIdProperty());
}
if (config.getOutputFormat() == OutputFormat.EXTJS5
&& StringUtils.hasText(model.getVersionProperty())) {
configObject.put("versionProperty", model.getVersionProperty());
}
configObject.put("fields", fields.values());
if (!model.getAssociations().isEmpty()) {
configObject.put("associations", model.getAssociations());
}
if (!model.getValidations().isEmpty()
&& !(config.getOutputFormat() == OutputFormat.EXTJS5)) {
configObject.put("validations", model.getValidations());
}
ProxyObject proxyObject = new ProxyObject(model, config);
if (proxyObject.hasContent()) {
configObject.put("proxy", proxyObject);
}
if (config.getOutputFormat() == OutputFormat.EXTJS4
|| config.getOutputFormat() == OutputFormat.EXTJS5) {
modelObject.putAll(configObject);
}
else {
modelObject.put("config", configObject);
}
StringBuilder sb = new StringBuilder();
sb.append("Ext.define(\"").append(model.getName()).append("\",");
if (config.isDebug()) {
sb.append("\n");
}
String configObjectString;
Class<?> jsonView = JsonViews.ExtJS4andTouch2.class;
if (config.getOutputFormat() == OutputFormat.EXTJS5) {
jsonView = JsonViews.ExtJS5.class;
}
try {
if (config.isDebug()) {
configObjectString = mapper.writerWithDefaultPrettyPrinter()
.withView(jsonView).writeValueAsString(modelObject);
}
else {
configObjectString = mapper.writerWithView(jsonView).writeValueAsString(
modelObject);
}
}
catch (JsonGenerationException e) {
throw new RuntimeException(e);
}
catch (JsonMappingException e) {
throw new RuntimeException(e);
}
catch (IOException e) {
throw new RuntimeException(e);
}
sb.append(configObjectString);
sb.append(");");
String result = sb.toString();
if (config.isUseSingleQuotes()) {
result = result.replace('"', '\'');
}
if (!config.isDebug()) {
jsCache.put(new JsCacheKey(model, config), new SoftReference<String>(result));
}
return result;
}
private static Set<String> addValidatorsToField(Map<String, ModelFieldBean> fields,
List<AbstractValidation> validations) {
Set<String> requires = new TreeSet<String>();
for (ModelFieldBean field : fields.values()) {
for (AbstractValidation validation : validations) {
if (field.getName().equals(validation.getField())) {
List<AbstractValidation> validators = field.getValidators();
if (validators == null) {
validators = new ArrayList<AbstractValidation>();
field.setValidators(validators);
}
String validatorClass = getValidatorClass(validation.getType());
if (validatorClass != null) {
requires.add(validatorClass);
}
boolean alreadyExists = false;
for (AbstractValidation validator : validators) {
if (validation.getType().equals(validator.getType())) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
validators.add(validation);
}
}
}
}
return requires;
}
private static String getValidatorClass(String type) {
if (type.equals("email")) {
return "Ext.data.validator.Email";
}
else if (type.equals("exclusion")) {
return "Ext.data.validator.Exclusion";
}
else if (type.equals("format")) {
return "Ext.data.validator.Format";
}
else if (type.equals("inclusion")) {
return "Ext.data.validator.Inclusion";
}
else if (type.equals("length")) {
return "Ext.data.validator.Length";
}
else if (type.equals("presence")) {
return "Ext.data.validator.Presence";
}
else if (type.equals("range")) {
return "Ext.data.validator.Range";
}
return null;
}
private static String trimToNull(String str) {
String trimmedStr = StringUtils.trimWhitespace(str);
if (StringUtils.hasLength(trimmedStr)) {
return trimmedStr;
}
return null;
}
/**
* Clears the model and Javascript code caches
*/
public static void clearCaches() {
modelCache.clear();
jsCache.clear();
}
}