/*
* Copyright 2015 Alidays S.p.A.
*
* 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 it.alidays.mapengine.codegenerator;
import it.alidays.mapengine.configuration.Configuration;
import it.alidays.mapengine.core.database.DatabaseManager;
import it.alidays.mapengine.core.map.AbstractRetrieve;
import it.alidays.mapengine.core.map.RetrieveHandler;
import it.alidays.mapengine.core.schema.SchemaHandler;
import it.alidays.mapengine.core.schema.SchemaHandlerException;
import it.alidays.mapengine.core.schema.converter.TypeConverterFactory;
import it.alidays.mapengine.core.schema.converter.TypeConverterFactoryException;
import it.alidays.mapengine.enginedirectives.EngineDirectives;
import it.alidays.mapengine.enginedirectives.map.Retrieve;
import it.alidays.mapengine.util.ResourceRetriever;
import it.alidays.mapengine.util.Utils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBException;
import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
public class MapperEngineCodeGenerator {
private static final Logger logger = LoggerFactory.getLogger(MapperEngineCodeGenerator.class);
public static void main(String[] args) throws MapperEngineCodeGeneratorException {
if (args.length != 2) {
printUsage();
}
else {
String destinationDir = null;
String[] engineDirectivesSources = null;
for (String arg : args) {
if (arg.startsWith("-d")) {
destinationDir = arg.substring(2);
}
else if (arg.startsWith("-s")) {
engineDirectivesSources = arg.substring(2).split("\\|");
}
}
if (destinationDir == null || destinationDir.isEmpty() || engineDirectivesSources == null || engineDirectivesSources.length == 0) {
printUsage();
}
else {
for (String engineDirectivesSource : engineDirectivesSources) {
run(new File(destinationDir), MapperEngineCodeGenerator.class.getClassLoader().getResourceAsStream(engineDirectivesSource));
}
}
}
}
private static void run(File destinationDir, InputStream engineDirectivesSource) throws MapperEngineCodeGeneratorException {
logger.info("Code generation started...");
/*****************
* CONFIGURATION *
*****************/
Configuration configuration;
try {
configuration = ResourceRetriever.loadConfiguration();
}
catch (JAXBException jaxbe) {
throw new MapperEngineCodeGeneratorException("Failed to load configuration", jaxbe);
}
/**************************
* TYPE CONVERTER FACTORY *
**************************/
try {
TypeConverterFactory.initialize(configuration);
}
catch (TypeConverterFactoryException tcfe) {
throw new MapperEngineCodeGeneratorException("Failed initialize TypeConverterFactory", tcfe);
}
/*********************
* ENGINE DIRECTIVES *
*********************/
EngineDirectives engineDirectives;
try {
engineDirectives = ResourceRetriever.loadEngineDirectives(engineDirectivesSource);
}
catch (JAXBException jaxbe) {
throw new MapperEngineCodeGeneratorException("Failed to load engine directives", jaxbe);
}
DatabaseManager databaseManager = null;
try {
databaseManager = new DatabaseManager("jdbc:h2:mem:mapgenerator;DB_CLOSE_DELAY=-1", "mapgenerator", "mapgenerator");
}
catch (SQLException sqle) {
throw new MapperEngineCodeGeneratorException("Failed to initialize database manager", sqle);
}
SchemaHandler schemaHandler = null;
try {
schemaHandler = new SchemaHandler(engineDirectives.getFetch().getEntities(), databaseManager);
schemaHandler.create();
}
catch (SchemaHandlerException she) {
throw new MapperEngineCodeGeneratorException(she);
}
logger.info("--------------------------------------");
logger.info("*** GENERATING MAP CLASSES ***");
String packageName = engineDirectives.getMap().getMapPackage();
File realDestinationDir = new File(destinationDir, packageName.replaceAll("\\.", "/"));
if (!realDestinationDir.exists()) {
realDestinationDir.mkdirs();
}
File[] files = realDestinationDir.listFiles();
for (File file : files) {
file.delete();
}
if (engineDirectives.getMap().getRetrieves() != null) {
try (Connection connection = databaseManager.getConnection()) {
for (Retrieve retrieve : engineDirectives.getMap().getRetrieves()) {
manageRetrieve(retrieve, connection, packageName, destinationDir);
}
}
catch (Exception e) {
throw new MapperEngineCodeGeneratorException(e);
}
}
logger.info("--------------------------------------");
logger.info("*** GENERATING RETRIEVE ID ENUM ***");
if (engineDirectives.getMap().getRetrieves() != null) {
try {
createRetrieveIdEnum(engineDirectives.getMap().getRetrieves(), packageName, destinationDir);
}
catch (Exception e) {
throw new MapperEngineCodeGeneratorException(e);
}
}
logger.info("Code generation completed");
}
private static void manageRetrieve(Retrieve retrieve, Connection connection, String packageName, File destinationDir) throws SQLException,
JClassAlreadyExistsException,
IOException,
MapperEngineCodeGeneratorException {
logger.info("Generating map for {}", retrieve.getId());
int vuidCount = RetrieveHandler.getVuidCount(retrieve.getContent());
String content = retrieve.getContent().replaceAll(RetrieveHandler.VUID_KEY, "?");
Map<String, Integer> columns = new LinkedHashMap<>();
logger.info("\tRetrieving columns' name");
try (PreparedStatement preparedStatement = connection.prepareStatement(content)) {
for (int index = 1; index <= vuidCount; index++) {
preparedStatement.setObject(index, "_");
}
ResultSet resultSet = preparedStatement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
for (int i = 1, n = resultSetMetaData.getColumnCount(); i <= n; i++) {
String columnName = Utils.arrangeColumnName(resultSetMetaData.getColumnLabel(i));
Integer columnType = resultSetMetaData.getColumnType(i);
columns.put(columnName, columnType);
}
}
logger.info("\tRetrieved {} columns' name", columns.size());
createMapClass(retrieve, columns, packageName, destinationDir);
createRetrieveClass(retrieve, packageName, destinationDir);
logger.info("Map successfully generated for {}", retrieve.getId());
}
private static void createMapClass(Retrieve retrieve, Map<String, Integer> columns, String packageName, File destinationDir) throws JClassAlreadyExistsException,
MapperEngineCodeGeneratorException,
IOException {
JCodeModel codeModel = new JCodeModel();
JDefinedClass mapClass = codeModel._class(String.format("%s.%sMap", packageName, retrieve.getId()));
mapClass.javadoc().append("Auto generated class. Do not modify!");
// Constructor
JMethod constructor = mapClass.constructor(JMod.PROTECTED);
constructor.param(codeModel.ref(Map.class).narrow(String.class, Object.class), "data");
for (String column : columns.keySet()) {
String varName = WordUtils.uncapitalize(column);
Class<?> type = null;
switch (columns.get(column)) {
case Types.INTEGER:
type = Integer.class;
break;
case Types.VARCHAR:
type = String.class;
break;
case Types.DECIMAL:
type = BigDecimal.class;
break;
default:
throw new MapperEngineCodeGeneratorException(String.format("Missing Type map for column %s: %d", column, columns.get(column)));
}
JFieldVar field = mapClass.field(JMod.PRIVATE | JMod.FINAL, type, varName);
// Getter
JMethod getter = mapClass.method(JMod.PUBLIC, type, String.format("get%s", column));
getter.body()._return(JExpr._this().ref(field));
constructor.body().assign(JExpr._this().ref(varName), JExpr.cast(codeModel.ref(type), JExpr.ref("data").invoke("get").arg(column)));
}
codeModel.build(destinationDir);
}
private static void createRetrieveClass(Retrieve retrieve, String packageName, File destinationDir) throws JClassAlreadyExistsException, IOException {
JCodeModel codeModel = new JCodeModel();
JClass mapClass = codeModel.ref(String.format("%s.%sMap", packageName, retrieve.getId()));
JDefinedClass retrieveClass = codeModel._class(String.format("%s.%sRetrieve", packageName, retrieve.getId()));
retrieveClass.javadoc().append("Auto generated class. Do not modify!");
retrieveClass._extends(codeModel.ref(AbstractRetrieve.class).narrow(mapClass));
// Constructor
JMethod constructor = retrieveClass.constructor(JMod.PUBLIC);
constructor.param(String.class, "id");
constructor.body().invoke("super").arg(JExpr.ref("id"));
// Implemented method
JMethod getMapMethod = retrieveClass.method(JMod.PUBLIC, mapClass, "getMap");
getMapMethod.annotate(Override.class);
getMapMethod.param(codeModel.ref(Map.class).narrow(String.class, Object.class), "data");
getMapMethod.body()._return(JExpr._new(mapClass).arg(JExpr.ref("data")));
codeModel.build(destinationDir);
}
private static void createRetrieveIdEnum(List<Retrieve> retrieves, String packageName, File destinationDir) throws JClassAlreadyExistsException, IOException {
JCodeModel codeModel = new JCodeModel();
JDefinedClass enumClass = codeModel._class(JMod.PUBLIC, String.format("%s.MapEngineRetrieveId", packageName), ClassType.ENUM);
for (Retrieve retrieve : retrieves) {
enumClass.enumConstant(retrieve.getId());
}
codeModel.build(destinationDir);
}
private static void printUsage() {
System.out.println("MapperEngineCodeGenerator -d[destination-dir] -s[engine-directives-sources]");
}
private MapperEngineCodeGenerator() {
}
}