Utility.java

/*
 * Copyright 2023 Gregory Graham.
 *
 * Commercial licenses are available, please contact info@gregs.co.nz for details.
 * 
 * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 
 * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
 * 
 * You are free to:
 *     Share - copy and redistribute the material in any medium or format
 *     Adapt - remix, transform, and build upon the material
 * 
 *     The licensor cannot revoke these freedoms as long as you follow the license terms.               
 *     Under the following terms:
 *                 
 *         Attribution - 
 *             You must give appropriate credit, provide a link to the license, and indicate if changes were made. 
 *             You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
 *         NonCommercial - 
 *             You may not use the material for commercial purposes.
 *         ShareAlike - 
 *             If you remix, transform, or build upon the material, 
 *             you must distribute your contributions under the same license as the original.
 *         No additional restrictions - 
 *             You may not apply legal terms or technological measures that legally restrict others from doing anything the 
 *             license permits.
 * 
 * Check the Creative Commons website for any details, legalese, and updates.
 */
package nz.co.gregs.dbvolution.generation;

import java.sql.Types;
import java.util.Arrays;
import java.util.List;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.datatypes.*;
import nz.co.gregs.dbvolution.exceptions.NoAvailableDatabaseException;
import nz.co.gregs.dbvolution.exceptions.UnknownJavaSQLTypeException;

/**
 *
 * @author gregorygraham
 */
public class Utility {

	private Utility() {

	}

	public static final String[] JAVA_RESERVED_WORDS_ARRAY = new String[]{"null", "true", "false", "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", "boolean", "do", "if", "private", "this", "break", "double", "implements", "", "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", "", "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while"};
	public static final List<String> JAVA_RESERVED_WORDS = Arrays.asList(JAVA_RESERVED_WORDS_ARRAY);

	/**
	 *
	 * Returns a string of the appropriate QueryableDatatype for the specified
	 * SQLType
	 *
	 *
	 *
	 *
	 *
	 * @param database the database we're using to generate classes
	 * @param typeName the name of the type
	 * @param columnType the JDBC defined column type
	 * @param precision the number assigned to the size of the type
	 * @param trimCharColumns do you want to automatically remove leading and trailing spaces on your VARCHARs: yes/no
	 * @return a string of the appropriate QueryableDatatype for the specified
	 * SQLType
	 */
	public static Class<? extends Object> getQDTClassOfSQLType(DBDatabase database, String typeName, int columnType, int precision, Boolean trimCharColumns) throws UnknownJavaSQLTypeException {

		Class<? extends Object> value;
		switch (columnType) {
			case Types.BIT:
				if (precision == 1) {
					value = checkForSimulatedTypesOrUseDefault(database, typeName, DBBoolean.class);
				} else {
					value = checkForSimulatedTypesOrUseDefault(database, typeName, DBLargeBinary.class);
				}
				break;
			case Types.TINYINT:
			case Types.INTEGER:
			case Types.BIGINT:
			case Types.BOOLEAN:
			case Types.ROWID:
			case Types.SMALLINT:
				if (precision == 1) {
					value = checkForSimulatedTypesOrUseDefault(database, typeName, DBBoolean.class);
				} else {
					value = checkForSimulatedTypesOrUseDefault(database, typeName, DBInteger.class);
				}
				break;
			case Types.DECIMAL:
			case Types.DOUBLE:
			case Types.FLOAT:
			case Types.NUMERIC:
			case Types.REAL:
				value =checkForSimulatedTypesOrUseDefault(database, typeName, DBNumber.class);
				break;
			case Types.CHAR:
			case Types.NCHAR:
				if (trimCharColumns) {
					value = checkForSimulatedTypesOrUseDefault(database, typeName, DBStringTrimmed.class);
				} else {
					value = checkForSimulatedTypesOrUseDefault(database, typeName, DBString.class);
				}
				break;
			case Types.VARCHAR:
			case Types.NVARCHAR:
			case Types.LONGNVARCHAR:
			case Types.LONGVARCHAR:
			value = checkForSimulatedTypesOrUseDefault(database, typeName, DBString.class);
			break;
			case Types.DATE:
			case Types.TIME:
			case Types.TIMESTAMP:
				value = checkForSimulatedTypesOrUseDefault(database, typeName, DBDate.class);
				break;
			case Types.OTHER:
			value = checkForSimulatedTypesOrUseDefault(database, typeName, DBJavaObject.class);
			break;
			case Types.JAVA_OBJECT:
				value = checkForSimulatedTypesOrUseDefault(database, typeName, DBJavaObject.class);
				break;
			case Types.CLOB:
			case Types.NCLOB:
				value = checkForSimulatedTypesOrUseDefault(database, typeName, DBLargeText.class);
				break;
			case Types.BINARY:
			case Types.VARBINARY:
			case Types.LONGVARBINARY:
			case Types.BLOB:
			case Types.ARRAY:
			case Types.SQLXML:
			value = checkForSimulatedTypesOrUseDefault(database, typeName, DBLargeBinary.class);
			break;
			default:
				throw new UnknownJavaSQLTypeException("Unknown Java SQL Type: " + columnType, columnType);
		}
		return value;
	}

	private static Class<? extends Object> checkForSimulatedTypesOrUseDefault(DBDatabase database, String typeName, final Class<?> defaultClass) throws NoAvailableDatabaseException {
		Class<? extends Object> value;
		Class<? extends QueryableDatatype<?>> customStringType = database.getDefinition().getQueryableDatatypeClassForSQLDatatype(typeName);
		if (customStringType != null) {
			value = customStringType;
			return value;
		} else {
			value = defaultClass;
			return value;
		}
	}

	/**
	 *
	 * returns a good guess at the java CLASS version of a DB field name.
	 *
	 * I.e. changes "_" into an uppercase letter.
	 *
	 * @param s	s
	 *
	 *
	 * @return camel case version of the String
	 */
	public static String toClassCase(String s) {
		StringBuilder classCaseString = new StringBuilder("");
		if (s == null) {
			return null;
		} else if (s.matches("[lLtT]+_[0-9]+(_[0-9]+)*")) {
			classCaseString.append(s.toUpperCase());
		} else {
			String[] parts = s.split("[^a-zA-Z0-9]");
			for (String part : parts) {
				classCaseString.append(toProperCase(part));
			}
		}
		return classCaseString.toString();
	}

	/**
	 *
	 * Capitalizes the first letter of the string
	 *
	 *
	 *
	 *
	 *
	 * @param s the string to change
	 * @return Capitalizes the first letter of the string
	 */
	public static String toProperCase(String s) {
		switch (s.length()) {
			case 0:
				return s;
			case 1:
				return s.toUpperCase();
			default:
				String firstChar = s.substring(0, 1);
				String rest;
				if (s.replaceAll("[^A-Z]", "").length() > 0
						&& s.replaceAll("[^a-z]", "").length() > 0) {
					rest = s.substring(1);//.toLowerCase();
				} else {
					rest = s.substring(1).toLowerCase();
				}
				if (firstChar.matches("[^a-zA-Z]")) {
					return "_" + firstChar + rest;
				} else {
					return firstChar.toUpperCase() + rest;
				}
		}
	}

	/**
	 *
	 * returns a good guess at the java field version of a DB field name.I.e.
	 *
	 * changes "_" into an uppercase letter.
	 *
	 *
	 *
	 *
	 *
	 * @param s the string to change
	 * @return Camel Case version of S
	 */
	public static String toFieldCase(String s) {
		String classClass = Utility.toClassCase(s);
		String camelCaseString = classClass.substring(0, 1).toLowerCase() + classClass.substring(1);
		camelCaseString = camelCaseString.replaceAll("[^a-zA-Z0-9_$]", "_");
		if (JAVA_RESERVED_WORDS.contains(camelCaseString)) {
			camelCaseString += "_";
		}
		return camelCaseString;
	}
}