/**********************************************************************
* This file is part of Adempiere ERP Bazaar                           *
* http://www.adempiere.org                                            *
*                                                                     *
* Copyright (C) Trifon Trifonov.                                      *
* Copyright (C) Contributors                                          *
*                                                                     *
* This program is free software, you can redistribute it and/or       *
* modify it under the terms of the GNU General Public License         *
* as published by the Free Software Foundation, either version 2      *
* 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 Public License for more details.                        *
*                                                                     *
* You should have received a copy of the GNU General Public License   *
* along with this program, if not, write to the Free Software         *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
* MA 02110-1301, USA.                                                 *
*                                                                     *
* Contributors:                                                       *
* - Trifon Trifonov (trifonnt@users.sourceforge.net)                  *
* - Teo Sarca (teo.sarca@arhipac.ro)                                  *
*                                                                     *
* Sponsors:                                                           *
* - Company (http://www.d3-soft.com)                                  *
* - ARHIPAC (http://www.arhipac.ro)                                   *
**********************************************************************/

package org.adempiere.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.logging.Level;

import org.adempiere.exceptions.DBException;
import org.compiere.Adempiere;
import org.compiere.model.MEntityType;
import org.compiere.model.MQuery;
import org.compiere.model.MTable;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.Util;

/**
 *  Generate interface class for model 
 *	@author Trifon Trifonov
 *
 *  @author Teo Sarca, SC ARHIPAC SERVICE SRL
 * 				<li>BF [ 1781629 ] Don't use Env.NL in model class/interface generators
 * 				<li>FR [ 1781630 ] Generated class/interfaces have a lot of unused imports
 * 				<li>BF [ 1781632 ] Generated class/interfaces should be UTF-8
 * 				<li>better formating of generated source
 * 				<li>BF [ 1787833 ] ModelInterfaceGenerator: don't write timestamp
 * 				<li>FR [ 1803309 ] Model generator: generate get method for Search cols
 * 				<li>BF [ 1817768 ] Isolate hardcoded table direct columns
 * 					https://sourceforge.net/p/adempiere/bugs/827/
 * 				<li>FR [ 2343096 ] Model Generator: Improve Reference Class Detection
 * 				<li>BF [ 2528434 ] ModelInterfaceGenerator: generate getters for common fields
 * 				<li>--
 * 				<li>FR [ 2848449 ] ModelClassGenerator: Implement model getters
 *					https://sourceforge.net/p/adempiere/feature-requests/812/
 *  @author Teo Sarca, teo.sarca@gmail.com
 * 				<li>FR [ 3020635 ] Model Generator should use FQ class names
 * 					https://sourceforge.net/p/adempiere/feature-requests/987/
 *  @author Victor Perez, e-Evolution
 * 				<li>FR [ 1785001 ] Using ModelPackage of EntityType to Generate Model Class
 */
public class ModelInterfaceGenerator
{
	private String packageName = "";

	public static final String NL = "\n";

	/** File Header			*/
	public static final String COPY =
		 "/******************************************************************************\n"
		+" * Product: iDempiere ERP & CRM Smart Business Solution                       *\n"
		+" * Copyright (C) 1999-2012 ComPiere, Inc. All Rights Reserved.                *\n"
		+" * This program is free software, you can redistribute it and/or modify it    *\n"
		+" * under the terms version 2 of the GNU General Public License as published   *\n"
		+" * by the Free Software Foundation. This program is distributed in the hope   *\n"
		+" * that it will be useful, but WITHOUT ANY WARRANTY, without even the implied *\n"
		+" * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.           *\n"
		+" * See the GNU General Public License for more details.                       *\n"
		+" * You should have received a copy of the GNU General Public License along    *\n"
		+" * with this program, if not, write to the Free Software Foundation, Inc.,    *\n"
		+" * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.                     *\n"
		+" * For the text or an alternative of this public license, you may reach us    *\n"
		+" * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA        *\n"
		+" * or via info@compiere.org or http://www.compiere.org/license.html           *\n"
		+" *****************************************************************************/\n";

	/** Logger */
	private static final CLogger log = CLogger.getCLogger(ModelInterfaceGenerator.class);

	public final static String GEN_SOURCE_INTERFACE = "I";
	public final static String GEN_SOURCE_CLASS = "C";
	
	/**
	 * @param AD_Table_ID
	 * @param directory
	 * @param packageName
	 * @param entityTypeFilter entity type filter for column
	 */
	public ModelInterfaceGenerator(int AD_Table_ID, String directory, String packageName, String entityTypeFilter) {
		this.packageName = packageName;
		// create column access methods
		StringBuilder mandatory = new StringBuilder();
		StringBuilder sb = createColumns(AD_Table_ID, mandatory, entityTypeFilter);

		// Header
		String tableName = createHeader(AD_Table_ID, sb, mandatory);

		// Save
		if (directory.endsWith("/") || directory.endsWith("\\"))
		{

		} else {
			directory = directory + File.separator;
		}
		writeToFile(sb, directory + tableName + ".java");
	}

	/**
	 * Add Header info to buffer
	 *
	 * @param AD_Table_ID	table
	 * @param sb			buffer
	 * @param mandatory		init call for mandatory columns
	 * @param packageName	package name
	 * @return class name
	 */
	private String createHeader(int AD_Table_ID, StringBuilder sb, StringBuilder mandatory) {
		String tableName = "";
		int accessLevel = 0;
		String sql = "SELECT TableName, AccessLevel FROM AD_Table WHERE AD_Table_ID=?";
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = DB.prepareStatement(sql, null);
			pstmt.setInt(1, AD_Table_ID);
			rs = pstmt.executeQuery();
			if (rs.next())
			{
				tableName = rs.getString(1);
				accessLevel = rs.getInt(2);
			}
		}
		catch (SQLException e)
		{
			throw new DBException(e, sql);
		}
		finally
		{
			DB.close(rs, pstmt);
			rs = null; pstmt = null;
		}
		if (tableName == null)
			throw new RuntimeException("TableName not found for ID=" + AD_Table_ID);
		//
		StringBuilder accessLevelInfo = new StringBuilder().append(accessLevel).append(" ");
		if (accessLevel >= 4 )
			accessLevelInfo.append("- System ");
		if (accessLevel == 2 || accessLevel == 3 || accessLevel == 6 || accessLevel == 7)
			accessLevelInfo.append("- Client ");
		if (accessLevel == 1 || accessLevel == 3 || accessLevel == 5 || accessLevel == 7)
			accessLevelInfo.append("- Org ");

		//
		StringBuilder className = new StringBuilder("I_").append(tableName);
		//
		StringBuilder start = new StringBuilder()
			.append (COPY)
			.append("package ").append(packageName).append(";").append(NL)
		;

		if (!packageName.equals("org.compiere.model")) {
			addImportClass("org.compiere.model.*");
		}
		addImportClass(java.math.BigDecimal.class);
		addImportClass(org.compiere.util.KeyNamePair.class);

		createImports(start);
		// Interface
		start.append("/** Generated Interface for ").append(tableName).append("\n")
			 .append(" *  @author iDempiere (generated) \n")
			 .append(" *  @version ").append(Adempiere.MAIN_VERSION).append(NL) //.append(" - ").append(s_run).append("\n")
			 .append(" */\n");
		if (!packageName.equals("org.compiere.model")) {
			 start.append("@SuppressWarnings(\"all\")\n");
		}
		start.append("public interface ").append(className).append(" {").append("\n")

			 .append("    /** TableName=").append(tableName).append(" */\n")
			 .append("    public static final String Table_Name = \"").append(tableName).append("\";\n")

			 .append("    /** AD_Table_ID=").append(AD_Table_ID).append(" */\n");

		//MTable.getTable_ID unnecessary for official ID
		if (AD_Table_ID <= MTable.MAX_OFFICIAL_ID)
			start.append("    public static final int Table_ID = ").append(AD_Table_ID).append(";\n");
		else
			start.append("    public static final int Table_ID = MTable.getTable_ID(Table_Name);\n");

		start.append("    KeyNamePair Model = new KeyNamePair(Table_ID, Table_Name);\n") // INFO - Should this be here???

			 .append("    /** AccessLevel = ").append(accessLevelInfo).append("\n")
			 .append("     */\n")
			 .append("    BigDecimal accessLevel = BigDecimal.valueOf(").append(accessLevel).append(");\n") // INFO - Should this be here???

			 .append("    /** Load Meta Data */\n")
		;

		String end = "}";
		//
		sb.insert(0, start);
		sb.append(end);

		return className.toString();
	}

	/**
	 * Create Column access methods
	 *
	 * @param AD_Table_ID table
	 * @param mandatory   init call for mandatory columns
	 * @param entityTypeFilter
	 * @return set/get method
	 */
	private StringBuilder createColumns(int AD_Table_ID, StringBuilder mandatory, String entityTypeFilter) {
		StringBuilder sb = new StringBuilder();
		String sql = "SELECT c.ColumnName, c.IsUpdateable, c.IsMandatory," // 1..3
				+ " c.AD_Reference_ID, c.AD_Reference_Value_ID, DefaultValue, SeqNo, " // 4..7
				+ " c.FieldLength, c.ValueMin, c.ValueMax, c.VFormat, c.Callout, " // 8..12
				+ " c.Name, c.Description, c.ColumnSQL, c.IsEncrypted, c.IsKey "   // 13..17
				+ "FROM AD_Column c "
				+ "WHERE c.AD_Table_ID=?"

				+ " AND c.IsActive='Y' AND (c.ColumnSQL IS NULL OR c.ColumnSQL NOT LIKE '@SQL%') "
				+ (!Util.isEmpty(entityTypeFilter) ? " AND c." + entityTypeFilter : "")
				+ " ORDER BY c.ColumnName";
		if (DB.isOracle())
			sql += " COLLATE \"BINARY\"";
		else if (DB.isPostgreSQL())
			sql += " COLLATE \"C\"";
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = DB.prepareStatement(sql, null);
			pstmt.setInt(1, AD_Table_ID);
			rs = pstmt.executeQuery();
			while (rs.next()) {
				String columnName = rs.getString(1);
				boolean isUpdateable = "Y".equals(rs.getString(2));
				boolean isMandatory = "Y".equals(rs.getString(3));
				int displayType = rs.getInt(4);
				int AD_Reference_Value_ID = rs.getInt(5);
				String defaultValue = rs.getString(6);
				int fieldLength = rs.getInt(8);
				String ValueMin = rs.getString(9);
				String ValueMax = rs.getString(10);
				String VFormat = rs.getString(11);
				String Callout = rs.getString(12);
				String Name = rs.getString(13);
				String Description = rs.getString(14);
				String ColumnSQL = rs.getString(15);
				boolean virtualColumn = ColumnSQL != null
						&& ColumnSQL.length() > 0;
				boolean IsEncrypted = "Y".equals(rs.getString(16));
				boolean IsKey = "Y".equals(rs.getString(17));

				// Create COLUMNNAME_ property (teo_sarca, [ 1662447 ])
				sb.append("\n")
				  .append("    /** Column name ").append(columnName).append(" */\n")
				  .append("    public static final String COLUMNNAME_").append(columnName)
				  .append(" = \"").append(columnName).append("\";");

				//
				sb.append(createColumnMethods(mandatory, columnName,
						isUpdateable, isMandatory, displayType,
						AD_Reference_Value_ID, fieldLength, defaultValue,
						ValueMin, ValueMax, VFormat, Callout, Name,
						Description, virtualColumn, IsEncrypted, IsKey, AD_Table_ID));
			}
		}
		catch (SQLException e)
		{
			throw new DBException(e, sql);
		}
		finally
		{
			DB.close(rs, pstmt);
			rs = null; pstmt = null;
		}
		return sb;
	}

	/**
	 * Create set/get methods for column
	 *
	 * @param mandatory        init call for mandatory columns
	 * @param columnName       column name
	 * @param isUpdateable     updateable
	 * @param isMandatory      mandatory
	 * @param displayType      display type
	 * @param AD_Reference_ID  validation reference
	 * @param fieldLength    int
	 * @param defaultValue   default value
	 * @param ValueMin       String
	 * @param ValueMax       String
	 * @param VFormat        String
	 * @param Callout        String
	 * @param Name           String
	 * @param Description    String
	 * @param virtualColumn  virtual column
	 * @param IsEncrypted    stored encrypted
	 * @return set/get method
	 */
	private String createColumnMethods(StringBuilder mandatory,
			String columnName, boolean isUpdateable, boolean isMandatory,
			int displayType, int AD_Reference_ID, int fieldLength,
			String defaultValue, String ValueMin, String ValueMax,
			String VFormat, String Callout, String Name, String Description,
			boolean virtualColumn, boolean IsEncrypted, boolean IsKey, int AD_Table_ID)
	{
		Class<?> clazz = getClass(columnName, displayType, AD_Reference_ID);
		String dataType = getDataTypeName(clazz, displayType);
		if (defaultValue == null)
			defaultValue = "";

		StringBuilder sb = new StringBuilder();

		if (isGenerateSetter(columnName))
		{
			// Create Java Comment
			generateJavaComment("Set", Name, Description, sb);
			// public void setColumn (xxx variable)
			sb.append("\tpublic void set").append(columnName).append(" (")
				.append(dataType).append(" ").append(columnName).append(");");
		}

		// ****** Get Comment ******
		generateJavaComment("Get", Name, Description, sb);

		sb.append("\tpublic ").append(dataType);
		if (clazz.equals(Boolean.class)) {
			sb.append(" is");
			if (columnName.toLowerCase().startsWith("is"))
				sb.append(columnName.substring(2));
			else
				sb.append(columnName);
		} else
			sb.append(" get").append(columnName);
		sb.append("();");
		//

		if (isGenerateModelGetter(columnName) && DisplayType.isID(displayType) && !IsKey)
		{
			String fieldName = getFieldName(columnName);
			String referenceClassName = getReferenceClassName(AD_Table_ID, columnName, displayType, AD_Reference_ID);
			//
			if (fieldName != null && referenceClassName != null)
			{
				sb.append("\n")
				  .append("\tpublic "+referenceClassName+" get").append(fieldName).append("() throws RuntimeException;");
			}
		}
		addImportClass(clazz);
		return sb.toString();
	}

	/**
	 * Generate javadoc comment for methods.
	 * @param startOfComment
	 * @param propertyName
	 * @param description
	 * @param result
	 */
	public void generateJavaComment(String startOfComment, String propertyName,	String description, StringBuilder result) {
		result.append("\n")
			  .append("\t/** ").append(startOfComment).append(" ")
			  .append(Util.maskHTML(propertyName));

		if (description != null && description.length() > 0)
			result.append(".\n\t  * ").append(Util.maskHTML(description)).append(NL);

		result.append("\t  */\n");
	}

	/**
	 * Write to file
	 *
	 * @param sb string buffer
	 * @param fileName file name
	 */
	private void writeToFile(StringBuilder sb, String fileName) {
		try {
			File out = new File(fileName);
			Writer fw = new OutputStreamWriter(new FileOutputStream(out, false), "UTF-8");
			for (int i = 0; i < sb.length(); i++) {
				char c = sb.charAt(i);
				// after
				if (c == ';' || c == '}') {
					fw.write(c);
					if (sb.substring(i + 1).startsWith("//"))
						fw.write('\t');
					else
						fw.write(NL);
				}
				// before & after
				else if (c == '{') {
					fw.write(NL);
					fw.write(c);
					fw.write(NL);
				} else
					fw.write(c);
			}
			fw.flush();
			fw.close();
			float size = out.length();
			size /= 1024;
			StringBuilder msgout = new StringBuilder().append(out.getAbsolutePath()).append(" - ").append(size).append(" kB");
			System.out.println(msgout.toString());
		} catch (Exception ex) {
			log.log(Level.SEVERE, fileName, ex);
			throw new RuntimeException(ex);
		}
	}

	/** Import classes */
	private Collection<String> s_importClasses = new TreeSet<String>();
	
	/**
	 * Add class name to class import list
	 * @param className
	 */
	private void addImportClass(String className) {
		if (className == null
				|| (className.startsWith("java.lang.") && !className.startsWith("java.lang.reflect."))
				|| className.startsWith(packageName+"."))
			return;
		for(String name : s_importClasses) {
			if (className.equals(name))
				return;
		}
		if (className.equals("byte[]")) {
			log.warning("Invalid type - "+className);
			return;
		}
		s_importClasses.add(className);
	}
	
	/**
	 * Add class to class import list
	 * @param cl
	 */
	private void addImportClass(Class<?> cl) {
		if (cl.isArray()) {
			cl = cl.getComponentType();
		}
		if (cl.isPrimitive())
			return;
		addImportClass(cl.getCanonicalName());
	}
	
	/**
	 * Generate java imports
	 * @param sb
	 */
	private void createImports(StringBuilder sb) {
		for (String name : s_importClasses) {
			sb.append("import ").append(name).append(";"); //.append(NL);
		}
		sb.append(NL);
	}

	/**
	 * Get class for given display type and reference
	 * @param displayType
	 * @param AD_Reference_ID
	 * @return class
	 */
	public static Class<?> getClass(String columnName, int displayType, int AD_Reference_ID)
	{
		// Handle Posted
		if (columnName.equalsIgnoreCase("Posted")
				|| columnName.equalsIgnoreCase("Processed")
				|| columnName.equalsIgnoreCase("Processing"))
		{
			return Boolean.class;
		}
		// Record_ID
		else if (columnName.equalsIgnoreCase("Record_ID"))
		{
			return Integer.class;
		}
		// Reference Table
		else if ((DisplayType.Table == displayType || DisplayType.Search == displayType)
				&& AD_Reference_ID > 0)
		{
			String sql = "SELECT c.AD_Reference_ID, c.AD_Reference_Value_ID"
						+" FROM AD_Ref_Table rt"
						+" INNER JOIN AD_Column c ON (c.AD_Column_ID=rt.AD_Key)"
						+" WHERE rt.AD_Reference_ID=?";
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			try
			{
				pstmt = DB.prepareStatement(sql, null);
				pstmt.setInt(1, AD_Reference_ID);
				rs = pstmt.executeQuery();
				if (rs.next())
				{
					displayType = rs.getInt(1);
					AD_Reference_ID = rs.getInt(2);
				}
				else
				{
					throw new IllegalStateException("Not found AD_Ref_Table/AD_Column - DisplayType="+displayType+", AD_Reference_ID="+AD_Reference_ID);
				}
			}
			catch (SQLException e)
			{
				throw new DBException(e, sql);
			}
			finally
			{
				DB.close(rs, pstmt);
				rs = null; pstmt = null;
			}
			//
			return getClass(columnName, displayType, AD_Reference_ID); // recursive call with new parameters
		}
		else if (displayType == DisplayType.Button && columnName.endsWith("_ID"))
		{
			return Integer.class;
		}
		else
		{
			return DisplayType.getClass(displayType, true);
		}
	}

	/**
	 * @param cl
	 * @param displayType
	 * @return Java data type name (without the package part)
	 */
	public static String getDataTypeName(Class<?> cl, int displayType)
	{
		String dataType = cl.getName();
		dataType = dataType.substring(dataType.lastIndexOf('.')+1);
		if (dataType.equals("Boolean")) {
			dataType = "boolean";
		} else if (dataType.equals("Integer")) {
			dataType = "int";
		} else if (displayType == DisplayType.Binary || displayType == DisplayType.Image) {
			dataType = "byte[]";
		}
		return dataType;
	}

	/**
	 * @param columnName
	 * @return true if a setter method should be generated
	 */
	public static boolean isGenerateSetter(String columnName)
	{
		return
			!"AD_Client_ID".equals(columnName)
			&& !"Created".equals(columnName)
			&& !"CreatedBy".equals(columnName)
			&& !"Updated".equals(columnName)
			&& !"UpdatedBy".equals(columnName)
		;
	}

	/**
	 * @param columnName
	 * @return true if a model getter method (method that is returning referenced PO) should be generated
	 */
	public static boolean isGenerateModelGetter(String columnName)
	{
		return
			!"AD_Client_ID".equals(columnName)
			&& !"AD_Org_ID".equals(columnName)
			&& !"CreatedBy".equals(columnName)
			&& !"UpdatedBy".equals(columnName)
		;
	}

	/**
	 * @param AD_Table_ID
	 * @param toEntityType
	 * @return true if a model getter method (method that is returning referenced PO) should be generated
	 */
	public static boolean isGenerateModelGetterForEntity(int AD_Table_ID, String toEntityType)
	{
		final String fromEntityType = DB.getSQLValueString(null, "SELECT EntityType FROM AD_Table where AD_Table_ID=?", AD_Table_ID);
		final MEntityType fromEntity = MEntityType.get(Env.getCtx(), fromEntityType);
		final MEntityType toEntity = MEntityType.get(Env.getCtx(), toEntityType);
		return
			// Same entities
			fromEntityType.equals(toEntityType)
			// Both are system entities
			|| (fromEntity.isSystemMaintained() && toEntity.isSystemMaintained())
			// Not Sys Entity referencing a Sys Entity
			|| (!fromEntity.isSystemMaintained() && toEntity.isSystemMaintained())
		;
	}

	/**
	 * Get EntityType Model Package.
	 * author Victor Perez - [ 1785001 ] Using ModelPackage of EntityType to Generate Model Class
	 * @param entityType
	 * @return Java package name or null 
	 */
	public static String getModelPackage(String entityType)
	{
		if ("D".equals(entityType))
			return "org.compiere.model";

		MEntityType entity = MEntityType.get(Env.getCtx(), entityType);
		if (entity != null)
			return entity.getModelPackage();
		return null;
	}

	/**
	 * @param columnName
	 * @return Java field name
	 */
	public static String getFieldName(String columnName)
	{
		String fieldName;
		if (columnName.endsWith("_ID_To"))
			fieldName = columnName.substring(0, columnName.length() - 6) + "_To";
		else
			fieldName = columnName.substring(0, columnName.length() - 3);
		return fieldName;
	}

	/**
	 * @param AD_Table_ID
	 * @param columnName
	 * @param displayType
	 * @param AD_Reference_ID
	 * @return Java class name or null
	 */
	public static String getReferenceClassName(int AD_Table_ID, String columnName, int displayType, int AD_Reference_ID)
	{
		String referenceClassName = null;
		//
		if (displayType == DisplayType.TableDir
				|| (displayType == DisplayType.Search && AD_Reference_ID == 0))
		{
			String refTableName = MQuery.getZoomTableName(columnName); // teo_sarca: BF [ 1817768 ] Isolate hardcoded table direct columns
			referenceClassName = "I_"+refTableName;

			MTable table = MTable.get(Env.getCtx(), refTableName);
			if (table != null)
			{
				String entityType = table.getEntityType();
				String modelpackage = getModelPackage(entityType) ;
				if (modelpackage != null)
				{
					referenceClassName = modelpackage+"."+referenceClassName;
				}
				if (!isGenerateModelGetterForEntity(AD_Table_ID, entityType))
				{
					referenceClassName = null;
				}
			}
			else
			{
				throw new RuntimeException("No table found for "+refTableName);
			}
		}
		else if (displayType == DisplayType.Table
				|| (displayType == DisplayType.Search && AD_Reference_ID > 0))
		{
			// do not generate model getter for Fact_Acct.Account_ID
			if (AD_Table_ID == 270 && columnName.equals("Account_ID"))
				return null;
			// do not generate model getter for GL_DistributionLine.Account_ID
			if (AD_Table_ID == 707 && columnName.equals("Account_ID"))
				return null;
			//
			final String sql = "SELECT t.TableName, t.EntityType, ck.AD_Reference_ID"
				+" FROM AD_Ref_Table rt"
				+" INNER JOIN AD_Table t ON (t.AD_Table_ID=rt.AD_Table_ID)"
				+" INNER JOIN AD_Column ck ON (ck.AD_Table_ID=rt.AD_Table_ID AND ck.AD_Column_ID=rt.AD_Key)"
				+" WHERE rt.AD_Reference_ID=?"
			;
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			try
			{
				pstmt = DB.prepareStatement(sql, null);
				pstmt.setInt(1, AD_Reference_ID);
				rs = pstmt.executeQuery();
				if (rs.next())
				{
					final String refTableName = rs.getString(1);
					final String entityType = rs.getString(2);
					final int refDisplayType = rs.getInt(3);
					if (refDisplayType == DisplayType.ID)
					{
						referenceClassName = "I_"+refTableName;
						String modelpackage = getModelPackage(entityType);
						if (modelpackage != null)
						{
							referenceClassName = modelpackage+"."+referenceClassName;
						}
						if (!isGenerateModelGetterForEntity(AD_Table_ID, entityType))
						{
							referenceClassName = null;
						}
					}
				}
			}
			catch (SQLException e)
			{
				throw new DBException(e, sql);
			}
			finally
			{
				DB.close(rs, pstmt);
				rs = null; pstmt = null;
			}
		}
		else if (displayType == DisplayType.Location)
		{
			referenceClassName = "I_C_Location";
		}
		else if (displayType == DisplayType.Locator)
		{
			referenceClassName = "I_M_Locator";
		}
		else if (displayType == DisplayType.Account)
		{
			referenceClassName = "I_C_ValidCombination";
		}
		else if (displayType == DisplayType.PAttribute)
		{
			referenceClassName = "I_M_AttributeSetInstance";
		}
		else
		{
			// TODO - Handle other types
		}
		//
		return referenceClassName;
	}


	/**
	 * String representation
	 *
	 * @return string representation
	 */
	public String toString() {
		StringBuilder sb = new StringBuilder("GenerateModel[").append("]");
		return sb.toString();
	}

	/**
	 * @param sourceFolder
	 * @param packageName
	 * @param entityType
	 * @param tableName table Like
	 * @param columnEntityType
	 */
	public static void generateSource(String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType)
	{
		generateSource(GEN_SOURCE_INTERFACE, sourceFolder, packageName, entityType, tableName, columnEntityType);
	}

	/**
	 * @param sourceFolder
	 * @param packageName
	 * @param entityType
	 * @param tableName table Like
	 * @param columnEntityType
	 */
	public static void generateSource(String type, String sourceFolder, String packageName, String entityType, String tableName, String columnEntityType)
	{
		if (sourceFolder == null || sourceFolder.trim().length() == 0)
			throw new IllegalArgumentException("Must specify source folder");

		File file = new File(sourceFolder);
		if (!file.exists())
			throw new IllegalArgumentException("Source folder doesn't exists. sourceFolder="+sourceFolder);

		if (packageName == null || packageName.trim().length() == 0)
			throw new IllegalArgumentException("Must specify package name");

		if (tableName == null || tableName.trim().length() == 0)
			throw new IllegalArgumentException("Must specify table name");

		StringBuilder tableLike = new StringBuilder().append(tableName.trim().toUpperCase().replaceAll("'", ""));

		StringBuilder entityTypeFilter = new StringBuilder();
		if (entityType != null && entityType.trim().length() > 0)
		{
			entityTypeFilter.append("EntityType IN (");
			StringTokenizer tokenizer = new StringTokenizer(entityType, ",");
			int i = 0;
			while(tokenizer.hasMoreTokens()) {
				StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim());
				if (!token.toString().startsWith("'") || !token.toString().endsWith("'"))
					token = new StringBuilder("'").append(token).append("'");
				if (i > 0)
					entityTypeFilter.append(",");
				entityTypeFilter.append(token);
				i++;
			}
			entityTypeFilter.append(")");
		}
		else
		{
			entityTypeFilter.append("EntityType IN ('U','A')");
		}

		StringBuilder directory = new StringBuilder().append(sourceFolder.trim());
		String packagePath = packageName.replace(".", File.separator);
		if (!(directory.toString().endsWith("/") || directory.toString().endsWith("\\")))
		{
			directory.append(File.separator);
		}
		if (File.separator.equals("/"))
			directory = new StringBuilder(directory.toString().replaceAll("[\\\\]", File.separator));
		else
			directory = new StringBuilder(directory.toString().replaceAll("[/]", File.separator));
		directory.append(packagePath);
		file = new File(directory.toString());
		if (!file.exists())
			file.mkdirs();

		//	complete sql
		String filterViews = null;
		if (tableLike.toString().contains("%")) {
			filterViews = " AND (TableName IN ('RV_WarehousePrice','RV_BPartner') OR IsView='N')"; 	//	special views
		}
		if (tableLike.toString().equals("'%'")) {
			filterViews += " AND TableName NOT LIKE 'W|_%' ESCAPE '|'"; 	//	exclude webstore from general model generator
		}
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT AD_Table_ID ")
			.append("FROM AD_Table ")
			.append("WHERE IsActive = 'Y' AND TableName NOT LIKE '%_Trl' ");
		// Autodetect if we need to use IN or LIKE clause - teo_sarca [ 3020640 ]
		if (tableLike.indexOf(",") == -1)
			sql.append(" AND UPPER(TableName) LIKE ").append(DB.TO_STRING(tableLike.toString()));
		else { // only specific tables
			StringBuilder finalTableLike = new StringBuilder("");
			for (String table : tableLike.toString().split(",")) {
				if (finalTableLike.length() > 0)
					finalTableLike.append(", ");

				finalTableLike.append(DB.TO_STRING(table.replaceAll("'", "").trim()));
			}

			sql.append(" AND UPPER(TableName) IN (").append(finalTableLike).append(")");
		}

		sql.append(" AND ").append(entityTypeFilter.toString());
		if (filterViews != null) {
			sql.append(filterViews);
		}
		sql.append(" ORDER BY TableName");

		//
		StringBuilder columnFilterBuilder = new StringBuilder();
		if (!Util.isEmpty(columnEntityType, true))
		{
			columnFilterBuilder.append("EntityType IN (");
			StringTokenizer tokenizer = new StringTokenizer(columnEntityType, ",");
			int i = 0;
			while(tokenizer.hasMoreTokens()) {
				StringBuilder token = new StringBuilder().append(tokenizer.nextToken().trim());
				if (!token.toString().startsWith("'") || !token.toString().endsWith("'"))
					token = new StringBuilder("'").append(token).append("'");
				if (i > 0)
					columnFilterBuilder.append(",");
				columnFilterBuilder.append(token);
				i++;
			}
			columnFilterBuilder.append(")");
		}
		String columnFilter = columnFilterBuilder.length() > 0 ? columnFilterBuilder.toString() : null;
		
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try
		{
			pstmt = DB.prepareStatement(sql.toString(), null);
			rs = pstmt.executeQuery();

			boolean isEmpty = true;
			while (rs.next())
			{
				isEmpty = false;
				if (type.equals(GEN_SOURCE_INTERFACE))
					new ModelInterfaceGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter);
				else if (type.equals(GEN_SOURCE_CLASS))
					new ModelClassGenerator(rs.getInt(1), directory.toString(), packageName, columnFilter);
			}
			
			if (isEmpty)
				System.out.println("No data found for the table with name " + tableName);
		}
		catch (SQLException e)
		{
			throw new DBException(e, sql.toString());
		}
		finally
		{
			DB.close(rs, pstmt);
			rs = null; pstmt = null;
		}
	}
}