/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* 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 com.speedment.generator.translator.internal.namer;
import com.speedment.generator.translator.namer.JavaLanguageNamer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.speedment.common.codegen.util.Formatting.ucfirst;
import static com.speedment.runtime.core.internal.util.sql.SqlUtil.unQuote;
import static com.speedment.runtime.core.util.CollectorUtil.toUnmodifiableSet;
import static com.speedment.runtime.core.util.CollectorUtil.unmodifiableSetOf;
import static java.util.Objects.requireNonNull;
/**
*
* @author Per Minborg
*/
public class JavaLanguageNamerImpl implements JavaLanguageNamer {
// From http://download.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
//
// Literals
final static Set<String> JAVA_LITERAL_WORDS = unmodifiableSetOf(
"true", "false", "null"
);
// Java reserved keywords
final static Set<String> JAVA_RESERVED_WORDS = unmodifiableSetOf(
// Unused
"const", "goto",
// The real ones...
"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"
);
final static Set<Class<?>> JAVA_BUILT_IN_CLASSES = unmodifiableSetOf(
Boolean.class,
Byte.class,
Character.class,
Double.class,
Float.class,
Integer.class,
Long.class,
Object.class,
Short.class,
String.class,
BigDecimal.class,
BigInteger.class,
boolean.class,
byte.class,
char.class,
double.class,
float.class,
int.class,
long.class,
short.class
);
final static Set<String> JAVA_DEFAULT_IMPORTS = unmodifiableSetOf("java.lang");
final static Set<String> JAVA_BUILT_IN_CLASS_WORDS = JAVA_BUILT_IN_CLASSES.stream().map(Class::getSimpleName).collect(toUnmodifiableSet());
final static Set<String> JAVA_USED_WORDS = Stream.of(
JAVA_LITERAL_WORDS,
JAVA_RESERVED_WORDS,
JAVA_BUILT_IN_CLASS_WORDS
)
.flatMap(Collection::stream)
.collect(toUnmodifiableSet());
static final Set<String> JAVA_USED_WORDS_LOWER_CASE = JAVA_USED_WORDS.stream()
.map(String::toLowerCase)
.collect(toUnmodifiableSet());
static final Character REPLACEMENT_CHARACTER = '_';
@Override
public String javaTypeName(final String externalName) {
requireNonNull(externalName);
return javaName(externalName, Character::toUpperCase);
}
@Override
public String javaVariableName(final String externalName) {
requireNonNull(externalName);
return javaName(externalName, Character::toLowerCase);
}
@Override
public String javaStaticFieldName(final String externalName) {
requireNonNull(externalName);
return toUnderscoreSeparated(javaNameFromExternal(externalName)).toUpperCase();
}
@Override
public String javaPackageName(final String externalName) {
requireNonNull(externalName);
return replaceIfIllegalJavaIdentifierCharacter(
replaceIfJavaUsedWord(
externalName.replace(" ", "") // For package name, just remove all spaces (see #167)
)
).toLowerCase();
}
@Override
public String javaName(final String externalName, Function<Character, Character> mutator) {
final StringBuilder sb = new StringBuilder(javaNameFromExternal(externalName));
int startIndex = 0;
for (int i = 0; i < externalName.length(); i++) {
if (Character.isAlphabetic(sb.charAt(i))) {
// Skip over any non alphabetic characers like "_"
startIndex = i;
break;
}
}
if (sb.length() > startIndex) {
sb.replace(startIndex, startIndex + 1, String.valueOf(mutator.apply(sb.charAt(startIndex))));
}
return sb.toString();
}
@Override
public String nameFromExternal(final String externalName) {
requireNonNull(externalName);
String result = unQuote(externalName.trim()); // Trim if there are initial spaces or trailing spaces...
// CamelCase
// http://stackoverflow.com/questions/4050381/regular-expression-for-checking-if-capital-letters-are-found-consecutively-in-a
// [A-Z] -> \p{Lu}
// [^A-Za-z0-9] -> [^\pL0-90-9]
result = Stream.of(result.replaceAll("([\\p{Lu}]+)", "_$1").split("[^\\pL0-9]")).map(String::toLowerCase).map(s -> ucfirst(s)).collect(Collectors.joining());
return result;
}
@Override
public String javaNameFromExternal(final String externalName) {
requireNonNull(externalName);
return replaceIfIllegalJavaIdentifierCharacter(replaceIfJavaUsedWord(nameFromExternal(externalName)));
}
@Override
public String replaceIfJavaUsedWord(final String word) {
requireNonNull(word);
// We need to replace regardless of case because we do not know how the retuned string is to be used
if (JAVA_USED_WORDS_LOWER_CASE.contains(word.toLowerCase())) {
// If it is a java reseved/literal/class, add a "_" at the end to avoid naming conflics
return word + "_";
}
return word;
}
@Override
public String replaceIfIllegalJavaIdentifierCharacter(final String word) {
requireNonNull(word);
if (word.isEmpty()) {
return REPLACEMENT_CHARACTER.toString(); // No name is translated to REPLACEMENT_CHARACTER only
}
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < word.length(); i++) {
Character c = word.charAt(i);
if (i == 0) {
if (Character.isJavaIdentifierStart(c)) {
// Fine! Just add the first character
sb.append(c);
} else if (Character.isJavaIdentifierPart(c)) {
// Not ok as the first, but ok otherwise. Add the replacement before it
sb.append(REPLACEMENT_CHARACTER).append(c);
} else {
// Cannot be used as a java identifier. Replace it
sb.append(REPLACEMENT_CHARACTER);
}
} else if (Character.isJavaIdentifierPart(c)) {
// Fine! Just add it
sb.append(c);
} else {
// Cannot be used as a java identifier. Replace it
sb.append(REPLACEMENT_CHARACTER);
}
}
return sb.toString();
// We need to replace regardless of case because we do not know how the retuned string is to be used
//if (JAVA_USED_WORDS_LOWER_CASE.contains(word.toLowerCase())) {
// If it is a java reseved/literal/class, add a "_" at the end to avoid naming conflics
// return word + "_";
//}
//return word;
}
@Override
public String javaObjectName(final String javaTypeName) {
requireNonNull(javaTypeName);
String result = null;
if (javaTypeName.startsWith("int")) {
result = Integer.class.getSimpleName();
} else if (javaTypeName.startsWith("long")) {
result = Long.class.getSimpleName();
} else if (javaTypeName.startsWith("double")) {
result = Double.class.getSimpleName();
} else if (javaTypeName.startsWith("float")) {
result = Float.class.getSimpleName();
} else if (javaTypeName.startsWith("boolean")) {
result = Boolean.class.getSimpleName();
} else if (javaTypeName.startsWith("byte")) {
result = Byte.class.getSimpleName();
}
if (result != null) {
if (javaTypeName.endsWith("[]")) {
result += "[]";
}
} else {
result = javaTypeName;
}
return result;
}
@Override
public String toUnderscoreSeparated(final String javaName) {
requireNonNull(javaName);
final StringBuilder result = new StringBuilder();
final String input = unQuote(javaName.trim());
for (int i = 0; i < input.length(); i++) {
final char c = input.charAt(i);
if (result.length() == 0) {
result.append(Character.toLowerCase(c));
} else if (Character.isUpperCase(c)) {
result.append("_").append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
}