/* * 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. * * Other licenses: * ----------------------------------------------------------------------------- * Commercial licenses for this work are available. These replace the above * ASL 2.0 and offer limited warranties, support, maintenance, and commercial * database integrations. * * For more information, please visit: http://www.jooq.org/licenses * * * * * * * * * * * * * */ package org.jooq.util; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableSet; import static org.jooq.impl.DSL.name; import static org.jooq.util.AbstractGenerator.Language.JAVA; import static org.jooq.util.AbstractGenerator.Language.SCALA; import static org.jooq.util.GenerationUtil.ExpressionType.CONSTRUCTOR_REFERENCE; import static org.jooq.util.GenerationUtil.ExpressionType.EXPRESSION; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import org.jooq.Name; import org.jooq.SQLDialect; import org.jooq.exception.SQLDialectNotSupportedException; import org.jooq.util.AbstractGenerator.Language; import org.jooq.util.h2.H2DataType; /** * @author Lukas Eder * @author Eric Peters */ class GenerationUtil { static final Pattern TYPE_REFERENCE_PATTERN = Pattern.compile("^((?:[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)((?:<.*>|\\[.*\\])*)$"); static final Pattern PLAIN_GENERIC_TYPE_PATTERN = Pattern.compile("[<\\[]((?:[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)[>\\]]"); private static Set<String> JAVA_KEYWORDS = unmodifiableSet(new HashSet<String>(asList( "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "double", "do", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "interface", "int", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while"))); private static Set<String> SCALA_KEYWORDS = unmodifiableSet(new HashSet<String>(asList( "abstract", "case", "catch", "class", "def", "do", "else", "extends", "false", "final", "finally", "for", "forSome", "if", "implicit", "import", "lazy", "match", "new", "null", "object", "override", "package", "private", "protected", "return", "sealed", "super", "this", "throw", "trait", "try", "true", "type", "val", "var", "while", "with", "yield"/*, "_", ":", "=", "=>", "<-", "<:", "<%", ">:", "#", "@"*/ ))); private static Set<Character> SCALA_WHITESPACE = unmodifiableSet(new HashSet<Character>(asList( (char)0x0020, (char)0x0009, (char)0x000D, (char)0x000A ))); private static Set<Character> SCALA_PARENTHESES = unmodifiableSet(new HashSet<Character>(asList( '(', ')', '[', ']', '{', '}' ))); private static Set<Character> SCALA_DELIMITER = unmodifiableSet(new HashSet<Character>(asList( '`', '\'', '"', '.', ';', ',' ))); private static Set<String> WINDOWS_FORBIDDEN = unmodifiableSet(new HashSet<String>(asList( "CON", "PRN", "AUX", "CLOCK$", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" ))); /** * Take a character and determine if it's a valid "Scala Letter" * http://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html * * These consist of all printable ASCII characters \u0020 - \u007F which are in none of the sets above, mathematical symbols (Sm) and other symbols (So). * */ private static Boolean isScalaOperator(char c) { return (c >= 0x0020 && c <= 0x007F && !Character.isLetter(c) && !Character.isDigit(c) && !SCALA_DELIMITER.contains(c) && !SCALA_PARENTHESES.contains(c) && !SCALA_WHITESPACE.contains(c)) || Character.getType(c) == Character.MATH_SYMBOL /* Sm */ || Character.getType(c) == Character.OTHER_SYMBOL /* So */; } /** * Take a character and determine if it's a valid "Scala Letter" * http://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html * * Letters, which include lower case letters (Ll), upper case letters (Lu), titlecase letters (Lt), other letters (Lo), letter numerals (Nl) and the two characters \u0024 ‘$’ and \u005F ‘_’, which both count as upper case letters. * * Character.isLetter handles the Ll, Lu, Lt, Lo, and Nl, supplement with _ and $ * */ private static Boolean isScalaLetter(char c) { return Character.isLetter(c) || c == '_' || c == '$'; } /** * Take a character and determine if its a valid start of a scala identifier * http://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html * * Defines as a "scala letter", we're ignoring any identifiers that might starts with an operational character * */ private static Boolean isScalaIdentifierStart(char c) { return isScalaLetter(c); } /** * Take a character and determine if its a valid start of a scala identifier * http://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html * * Letters, which include lower case letters (Ll), upper case letters (Lu), titlecase letters (Lt), other letters (Lo), letter numerals (Nl) and the two characters \u0024 ‘$’ and \u005F ‘_’, which both count as upper case letters. * * Character.isLetter handles the Ll, Lu, Lt, Lo, and Nl, supplement with _ and $ */ private static Boolean isScalaIdentifierPart(char c) { return isScalaIdentifierStart(c) || Character.isDigit(c); } /** * Take a name and escape it if it is a Windows forbidden name like * <code>CON</code> or <code>AUX</code>. * * @see <a href="https://github.com/jOOQ/jOOQ/issues/5596">#5596</a> */ public static String escapeWindowsForbiddenNames(String name) { return name == null ? null : WINDOWS_FORBIDDEN.contains(name.toUpperCase()) ? name + "_" : name; } /** * Take a literal (e.g. database column) and make it a Java identifier to be * used without case-change as an enum identifier * <p> * [#959] These literals are escaped if they collide with reserved words. * This implementation is meant as a fix for [#959]. These types of * collisions have to be generally reviewed again, when allowing for more * control over generated source code, as of [#408][#911] * <p> * */ public static String convertToIdentifier(String literal, Language language) { if (language == JAVA && JAVA_KEYWORDS.contains(literal)) return literal + "_"; if (language == SCALA && SCALA_KEYWORDS.contains(literal)) return "`" + literal + "`"; StringBuilder sb = new StringBuilder(); if ("".equals(literal)) if (language == SCALA) return "`_`"; else return "_"; for (int i = 0; i < literal.length(); i++) { char c = literal.charAt(i); // [#5424] Scala setters, by convention, end in "property_=", where "=" is an operator and "_" precedes it if (language == SCALA && i == literal.length() - 1 && literal.length() >= 2 && literal.charAt(i - 1) == '_' && isScalaOperator(c)) sb.append(c); else if (language == SCALA && !isScalaIdentifierPart(c)) sb.append(escape(c)); else if (language == JAVA && !Character.isJavaIdentifierPart(c)) sb.append(escape(c)); else if (language == SCALA && i == 0 && !isScalaIdentifierStart(c)) sb.append("_").append(c); else if (language == JAVA && i == 0 && !Character.isJavaIdentifierStart(c)) sb.append("_").append(c); else sb.append(c); } return sb.toString(); } /** * @deprecated - Use {@link #convertToIdentifier(String, Language)} instead. */ @Deprecated public static String convertToJavaIdentifier(String literal) { return convertToIdentifier(literal, Language.JAVA); } private static String escape(char c) { if (c == ' ' || c == '-' || c == '.') return "_"; else return "_" + Integer.toHexString(c); } /** * Take a qualified Java type and make it a simple type * * @see Class#getSimpleName() */ static String getSimpleJavaType(String qualifiedJavaType) { if (qualifiedJavaType == null) { return null; } return qualifiedJavaType.replaceAll(".*\\.", ""); } /** * Gets the base type for an array type, depending on the RDBMS dialect */ static Name getArrayBaseType(SQLDialect dialect, String t, Name u) { // [#4388] TODO: Improve array handling switch (dialect.family()) { case POSTGRES: { // The convention is to prepend a "_" to a type to get an array type if (u != null && u.last().startsWith("_")) { String[] name = u.getName(); name[name.length - 1] = name[name.length - 1].substring(1); return name(name); } // But there are also arrays with a "vector" suffix else { return u; } } case H2: { return name(H2DataType.OTHER.getTypeName()); } case HSQLDB: { // In HSQLDB 2.2.5, there has been an incompatible INFORMATION_SCHEMA change around the // ELEMENT_TYPES view. Arrays are now described much more explicitly if ("ARRAY".equalsIgnoreCase(t)) { return name("OTHER"); } // This is for backwards compatibility else { return name(t.replace(" ARRAY", "")); } } } throw new SQLDialectNotSupportedException("getArrayBaseType() is not supported for dialect " + dialect); } /** * Generate a range between two bounds * * @param from The lower bound (inclusive) * @param to The upper bound (inclusive) * @return A range from <code>from</code> to <code>to</code> */ public static Integer[] range(Integer from, Integer to) { Integer[] result = new Integer[to - from + 1]; for (int i = from; i <= to; i++) { result[i - from] = i; } return result; } static ExpressionType expressionType(String expression) { if (TYPE_REFERENCE_PATTERN.matcher(expression).matches()) return CONSTRUCTOR_REFERENCE; else return EXPRESSION; } enum ExpressionType { CONSTRUCTOR_REFERENCE, EXPRESSION } }