/*
* 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 org.jooq.util.GenerationUtil.convertToIdentifier;
import static org.jooq.util.GenerationUtil.escapeWindowsForbiddenNames;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jooq.Record;
import org.jooq.impl.AbstractRoutine;
import org.jooq.impl.TableRecordImpl;
import org.jooq.impl.UDTRecordImpl;
import org.jooq.impl.UpdatableRecordImpl;
import org.jooq.tools.StringUtils;
import org.jooq.util.AbstractGenerator.Language;
/**
* A wrapper for generator strategies preventing some common compilation errors
* resulting from badly generated source code
*
* @author Lukas Eder
*/
class GeneratorStrategyWrapper extends AbstractGeneratorStrategy {
private final Map<Class<?>, Set<String>> reservedColumns = new HashMap<Class<?>, Set<String>>();
final Generator generator;
final GeneratorStrategy delegate;
final Language language;
GeneratorStrategyWrapper(Generator generator, GeneratorStrategy delegate, Language language) {
this.generator = generator;
this.delegate = delegate;
this.language = language;
}
@Override
public String getTargetDirectory() {
return delegate.getTargetDirectory();
}
@Override
public void setTargetDirectory(String directory) {
delegate.setTargetDirectory(directory);
}
@Override
public String getTargetPackage() {
return delegate.getTargetPackage();
}
@Override
public void setTargetPackage(String packageName) {
delegate.setTargetPackage(packageName);
}
@Override
public void setInstanceFields(boolean instanceFields) {
delegate.setInstanceFields(instanceFields);
}
@Override
public boolean getInstanceFields() {
return delegate.getInstanceFields();
}
@Override
public void setJavaBeansGettersAndSetters(boolean javaBeansGettersAndSetters) {
delegate.setJavaBeansGettersAndSetters(javaBeansGettersAndSetters);
}
@Override
public boolean getJavaBeansGettersAndSetters() {
return delegate.getJavaBeansGettersAndSetters();
}
@Override
public String getFileHeader(Definition definition, Mode mode) {
return delegate.getFileHeader(definition, mode);
}
@Override
public String getJavaIdentifier(Definition definition) {
String identifier = getFixedJavaIdentifier(definition);
if (identifier != null)
return identifier;
identifier = convertToIdentifier(delegate.getJavaIdentifier(definition), language);
// [#1212] Don't trust custom strategies and disambiguate identifiers here
if (definition instanceof ColumnDefinition ||
definition instanceof AttributeDefinition) {
TypedElementDefinition<?> e = (TypedElementDefinition<?>) definition;
if (identifier.equals(getJavaIdentifier(e.getContainer())))
return identifier + "_";
// [#2781] Disambiguate collisions with the leading package name
if (identifier.equals(getJavaPackageName(e.getContainer()).replaceAll("\\..*", "")))
return identifier + "_";
}
else if (definition instanceof TableDefinition) {
SchemaDefinition schema = definition.getSchema();
if (identifier.equals(getJavaIdentifier(schema)))
return identifier + "_";
}
// [#5557] Once more, this causes issues...
else if (definition instanceof SchemaDefinition) {
CatalogDefinition catalog = definition.getCatalog();
if (identifier.equals(getJavaIdentifier(catalog)))
return identifier + "_";
}
return identifier;
}
@Override
public String getJavaSetterName(Definition definition, Mode mode) {
return disambiguateMethod(definition,
convertToIdentifier(delegate.getJavaSetterName(definition, mode), language));
}
@Override
public String getJavaGetterName(Definition definition, Mode mode) {
return disambiguateMethod(definition,
convertToIdentifier(delegate.getJavaGetterName(definition, mode), language));
}
@Override
public String getJavaMethodName(Definition definition, Mode mode) {
String methodName;
methodName = delegate.getJavaMethodName(definition, mode);
methodName = overload(definition, mode, methodName);
methodName = convertToIdentifier(methodName, language);
return disambiguateMethod(definition, methodName);
}
/**
* [#1358] Add an overload suffix if needed
*/
private String overload(Definition definition, Mode mode, String identifier) {
if (!StringUtils.isBlank(definition.getOverload())) {
identifier += getOverloadSuffix(definition, mode, definition.getOverload());
}
return identifier;
}
/**
* [#182] Method name disambiguation is important to avoid name clashes due
* to pre-existing getters / setters in super classes
*/
private String disambiguateMethod(Definition definition, String method) {
Set<String> reserved = null;
if (definition instanceof AttributeDefinition) {
reserved = reservedColumns(UDTRecordImpl.class);
}
else if (definition instanceof ColumnDefinition) {
if (((ColumnDefinition) definition).getContainer().getPrimaryKey() != null) {
reserved = reservedColumns(UpdatableRecordImpl.class);
}
else {
reserved = reservedColumns(TableRecordImpl.class);
}
}
// [#1406] Disambiguate also procedure parameters
else if (definition instanceof ParameterDefinition) {
reserved = reservedColumns(AbstractRoutine.class);
}
if (reserved != null) {
if (reserved.contains(method)) {
return method + "_";
}
// If this is the setter, check if the getter needed disambiguation
// This ensures that getters and setters have the same name
if (method.startsWith("set")) {
String base = method.substring(3);
if (reserved.contains("get" + base) || reserved.contains("is" + base)) {
return method + "_";
}
}
}
return method;
}
/**
* [#182] Find all column names that are reserved because of the extended
* class hierarchy of a generated class
*/
private Set<String> reservedColumns(Class<?> clazz) {
if (clazz == null)
return Collections.emptySet();
Set<String> result = reservedColumns.get(clazz);
if (result == null) {
result = new HashSet<String>();
reservedColumns.put(clazz, result);
// Recurse up in class hierarchy
result.addAll(reservedColumns(clazz.getSuperclass()));
for (Class<?> c : clazz.getInterfaces())
result.addAll(reservedColumns(c));
for (Method m : clazz.getDeclaredMethods())
if (m.getParameterTypes().length == 0)
result.add(m.getName());
// [#5457] In Scala, we must not "override" any inherited members, even if they're private
// or package private, and thus not visible
if (language == Language.SCALA)
for (Field f : clazz.getDeclaredFields())
result.add(f.getName());
}
return result;
}
@Override
public String getJavaClassExtends(Definition definition, Mode mode) {
// [#1243] Only POJO mode can accept super classes
return delegate.getJavaClassExtends(definition, mode);
}
@Override
public List<String> getJavaClassImplements(Definition definition, Mode mode) {
// [#1243] All generation modes can accept interfaces
Set<String> result = new LinkedHashSet<String>(delegate.getJavaClassImplements(definition, mode));
// [#1528] Generated interfaces (implemented by RECORD and POJO) are
// always Serializable
if (mode == Mode.INTERFACE) {
result.add(Serializable.class.getName());
}
// [#1528] POJOs only implement Serializable if they don't inherit
// Serializable from INTERFACE already
else if (mode == Mode.POJO && !generator.generateInterfaces()) {
result.add(Serializable.class.getName());
}
return new ArrayList<String>(result);
}
@Override
public String getJavaClassName(Definition definition, Mode mode) {
String name = getFixedJavaClassName(definition);
if (name != null)
return name;
// [#1150] Intercept Mode.RECORD calls for tables
if (definition instanceof TableDefinition && !generator.generateRecords() && mode == Mode.RECORD)
return Record.class.getSimpleName();
String className;
className = delegate.getJavaClassName(definition, mode);
className = overload(definition, mode, className);
className = convertToIdentifier(className, language);
className = escapeWindowsForbiddenNames(className);
return className;
}
@Override
public String getJavaPackageName(Definition definition, Mode mode) {
// [#1150] Intercept Mode.RECORD calls for tables
if (!generator.generateRecords() && mode == Mode.RECORD && definition instanceof TableDefinition) {
return Record.class.getPackage().getName();
}
String[] split = delegate.getJavaPackageName(definition, mode).split("\\.");
for (int i = 0; i < split.length; i++) {
split[i] = convertToIdentifier(split[i], language);
split[i] = escapeWindowsForbiddenNames(split[i]);
}
return StringUtils
.join(split, ".")
// [#4168] In JDK 9, _ is no longer allowed as an identifier
.replaceAll("\\._?\\.", ".");
}
@Override
public String getJavaMemberName(Definition definition, Mode mode) {
String identifier = convertToIdentifier(delegate.getJavaMemberName(definition, mode), language);
// [#2781] Disambiguate collisions with the leading package name
if (identifier.equals(getJavaPackageName(definition, mode).replaceAll("\\..*", ""))) {
return identifier + "_";
}
return identifier;
}
@Override
public String getOverloadSuffix(Definition definition, Mode mode, String overloadIndex) {
return delegate.getOverloadSuffix(definition, mode, overloadIndex);
}
}