FreemarkerUtil.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2018 wcm.io
 * %%
 * 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.
 * #L%
 */
package io.wcm.qa.glnm.maven.freemarker.util;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import io.wcm.qa.glnm.configuration.GaleniumConfiguration;
import io.wcm.qa.glnm.exceptions.GaleniumException;
import io.wcm.qa.glnm.interaction.Element;
import io.wcm.qa.glnm.interaction.FormElement;
import io.wcm.qa.glnm.maven.freemarker.methods.ClassNameFromSelectorMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.ClassNameFromSpecMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.ClassNameFromStringMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.ConstantNameMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.EscapeJavaDocMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.EscapeJavaMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.FullSelectorClassNameFromSelectorMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.FullWebElementClassNameFromSelectorMethod;
import io.wcm.qa.glnm.maven.freemarker.methods.PackageNameMethod;
import io.wcm.qa.glnm.maven.freemarker.pojo.InteractionPojo;
import io.wcm.qa.glnm.maven.freemarker.pojo.SpecPojo;
import io.wcm.qa.glnm.selectors.base.NestedSelector;

/**
 * Utility methods to build data models for use in freemarker code generation.
 *
 * @since 1.0.0
 */
public final class FreemarkerUtil {

  private static final Configuration CONFIGURATION = generateConfiguration();

  private static final Logger LOG = LoggerFactory.getLogger(FreemarkerUtil.class);

  private FreemarkerUtil() {
    // do not instantiate
  }

  /**
   * <p>getDataModelForInteractiveSelector.</p>
   *
   * @param packageRoot package for interactive selector interface
   * @param interfaceName name of interactive selector interface to implement
   * @param className of interactive selector class
   * @return data model for generating interactive selector interface
   */
  public static Map<String, Object> getDataModelForInteractiveSelector(String packageRoot, String interfaceName, String className) {
    Map<String, Object> model = getCommonDataModel();
    model.put("elementInteraction", new InteractionPojo(Element.class));
    model.put("formElementInteraction", new InteractionPojo(FormElement.class));
    model.put("interactiveSelectorPackage", packageRoot);
    model.put("interactiveSelectorBaseClassName", className);
    model.put("interactiveSelectorInterfaceClassName", interfaceName);

    return model;
  }

  /**
   * <p>getDataModelForSelector.</p>
   *
   * @param selector selector to build data model for
   * @param spec selector is taken from
   * @param packageName package of interactive selector interface
   * @param interactiveClassName name of class
   * @param interactiveInterfaceName name of interactive selector interface
   * @return data model for use in generating selector class
   */
  public static Map<String, Object> getDataModelForSelector(
      NestedSelector selector,
      SpecPojo spec,
      String packageName,
      String interactiveClassName,
      String interactiveInterfaceName) {
    Map<String, Object> model = getDataModelForInteractiveSelector(packageName, interactiveInterfaceName, interactiveClassName);
    model.put("className", new ClassNameFromSelectorMethod());
    model.put("spec", spec);
    model.put("this", selector);

    return model;
  }

  /**
   * <p>getDataModelForSpec.</p>
   *
   * @param spec to generate Java class for
   * @param packagePrefixSpecs root package
   * @return data model for generating spec class
   */
  public static Map<String, Object> getDataModelForSpec(SpecPojo spec, String packagePrefixSpecs) {
    Map<String, Object> model = getCommonDataModel();
    model.put("className", new ClassNameFromSpecMethod());
    model.put("classNameFromString", new ClassNameFromStringMethod());
    model.put("specRootPackageName", packagePrefixSpecs);
    model.put("spec", spec);
    return model;
  }

  /**
   * <p>getDataModelForWebElement.</p>
   *
   * @param selector selector to build data model for
   * @param spec selector is taken from
   * @param packageName package of interactive selector interface
   * @param interactiveClassName name of interactive selector implementation
   * @param interactiveInterfaceName name of interactive selector interface
   * @return data model for use in generating selector class
   */
  public static Map<String, Object> getDataModelForWebElement(
      NestedSelector selector,
      SpecPojo spec,
      String packageName,
      String interactiveClassName,
      String interactiveInterfaceName) {
    Map<String, Object> model = getDataModelForSelector(selector, spec, packageName, interactiveClassName, interactiveInterfaceName);
    model.put("gweClassName", new FullWebElementClassNameFromSelectorMethod());
    model.put("selectorClassName", new FullSelectorClassNameFromSelectorMethod());
    return model;
  }

  /**
   * <p>getOutputFile.</p>
   *
   * @param outputDir directory
   * @param outputPackage package
   * @param outputClassName class name
   * @return file to write generated code to
   */
  public static File getOutputFile(File outputDir, String outputPackage, String outputClassName) {
    File outputDirectoryPackage = new File(outputDir, outputPackage.replaceAll("\\.", "/"));
    outputDirectoryPackage.mkdirs();
    File outputFile = new File(outputDirectoryPackage, outputClassName + ".java");
    return outputFile;
  }

  /**
   * <p>getTemplate.</p>
   *
   * @param directory template root folder
   * @param name name of template
   * @return freemarker template
   */
  public static Template getTemplate(File directory, String name) {
    try {
      CONFIGURATION.setDirectoryForTemplateLoading(directory);
      Template template = CONFIGURATION.getTemplate(name);
      return template;
    }
    catch (IOException ex) {
      throw new GaleniumException("exception when getting template.", ex);
    }
  }

  /**
   * Actually process template to generate code.
   *
   * @param template to process
   * @param dataModel to use
   * @param outputFile to write to
   */
  public static void process(Template template, Map<String, Object> dataModel, File outputFile) {
    FileWriter out = null;
    try {
      out = new FileWriter(outputFile);
      LOG.debug("generating '" + outputFile.getPath() + "'");
      template.process(dataModel, out);
      LOG.info("generated '" + outputFile.getPath() + "'");
    }
    catch (IOException | TemplateException ex) {
      throw new GaleniumException("template exception", ex);
    }
    finally {
      if (out != null) {
        try {
          out.close();
        }
        catch (IOException ex) {
          throw new GaleniumException("could not close writer when processing Freemarker template.", ex);
        }
      }
    }
  }

  private static Configuration generateConfiguration() {
    Configuration cfg = new Configuration();
    cfg.setDefaultEncoding("UTF-8");
    cfg.setTemplateExceptionHandler(getExceptionHandler());
    return cfg;
  }

  private static Map<String, Object> getCommonDataModel() {
    Map<String, Object> model = new HashMap<>();
    model.put("escapeXml", new EscapeJavaDocMethod());
    model.put("escapeJava", new EscapeJavaMethod());
    model.put("constantName", new ConstantNameMethod());
    model.put("packageName", new PackageNameMethod());
    return model;
  }

  private static TemplateExceptionHandler getExceptionHandler() {
    if (GaleniumConfiguration.isSparseReporting()) {
      return TemplateExceptionHandler.RETHROW_HANDLER;
    }
    else {
      return TemplateExceptionHandler.DEBUG_HANDLER;
    }
  }

}