Package ch.rasc.extclassgenerator

Source Code of ch.rasc.extclassgenerator.ModelGenerator

/**
* 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();
  }

}
TOP

Related Classes of ch.rasc.extclassgenerator.ModelGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.