FullyQualifiedTable.java
/*
* Copyright 2006-2026 the original author or authors.
*
* 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
*
* https://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 org.mybatis.generator.api;
import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
import static org.mybatis.generator.internal.util.StringUtility.mapStringValueOrElseGet;
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
import static org.mybatis.generator.internal.util.StringUtility.stringValueOrElseGet;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.DomainObjectRenamingRule;
import org.mybatis.generator.internal.util.JavaBeansUtil;
public class FullyQualifiedTable {
private final @Nullable String introspectedCatalog;
private final @Nullable String introspectedSchema;
private final String introspectedTableName;
private final @Nullable String runtimeCatalog;
private final @Nullable String runtimeSchema;
private final @Nullable String runtimeTableName;
private @Nullable String configuredDomainObjectName;
private @Nullable String domainObjectSubPackage;
private final @Nullable String alias;
private final boolean ignoreQualifiersAtRuntime;
private final String beginningDelimiter;
private final String endingDelimiter;
private final @Nullable DomainObjectRenamingRule domainObjectRenamingRule;
public FullyQualifiedTable(Builder builder) {
introspectedCatalog = builder.introspectedCatalog;
introspectedSchema = builder.introspectedSchema;
introspectedTableName = Objects.requireNonNull(builder.introspectedTableName);
ignoreQualifiersAtRuntime = builder.ignoreQualifiersAtRuntime;
runtimeCatalog = builder.runtimeCatalog;
runtimeSchema = builder.runtimeSchema;
runtimeTableName = builder.runtimeTableName;
domainObjectRenamingRule = builder.domainObjectRenamingRule;
if (stringHasValue(builder.domainObjectName)) {
int index = builder.domainObjectName.lastIndexOf('.');
if (index == -1) {
configuredDomainObjectName = builder.domainObjectName;
} else {
configuredDomainObjectName = builder.domainObjectName.substring(index + 1);
domainObjectSubPackage = builder.domainObjectName.substring(0, index);
}
}
if (builder.alias == null) {
alias = null;
} else {
this.alias = builder.alias.trim();
}
if (builder.delimitIdentifiers && builder.context != null) {
beginningDelimiter = builder.context.getBeginningDelimiter();
endingDelimiter = builder.context.getEndingDelimiter();
} else {
beginningDelimiter = ""; //$NON-NLS-1$
endingDelimiter = ""; //$NON-NLS-1$
}
}
public Optional<String> getIntrospectedCatalog() {
return Optional.ofNullable(introspectedCatalog);
}
public Optional<String> getIntrospectedSchema() {
return Optional.ofNullable(introspectedSchema);
}
public String getIntrospectedTableName() {
return introspectedTableName;
}
public String getFullyQualifiedTableNameAtRuntime() {
StringBuilder localCatalog = new StringBuilder();
if (!ignoreQualifiersAtRuntime) {
if (stringHasValue(runtimeCatalog)) {
localCatalog.append(runtimeCatalog);
} else if (stringHasValue(introspectedCatalog)) {
localCatalog.append(introspectedCatalog);
}
}
if (!localCatalog.isEmpty()) {
addDelimiters(localCatalog);
}
StringBuilder localSchema = new StringBuilder();
if (!ignoreQualifiersAtRuntime) {
if (stringHasValue(runtimeSchema)) {
localSchema.append(runtimeSchema);
} else if (stringHasValue(introspectedSchema)) {
localSchema.append(introspectedSchema);
}
}
if (!localSchema.isEmpty()) {
addDelimiters(localSchema);
}
StringBuilder localTableName = new StringBuilder();
if (stringHasValue(runtimeTableName)) {
localTableName.append(runtimeTableName);
} else {
localTableName.append(introspectedTableName);
}
addDelimiters(localTableName);
return composeFullyQualifiedTableName(localCatalog.toString(), localSchema.toString(),
localTableName.toString(), '.');
}
public String getAliasedFullyQualifiedTableNameAtRuntime() {
StringBuilder sb = new StringBuilder();
sb.append(getFullyQualifiedTableNameAtRuntime());
if (stringHasValue(alias)) {
sb.append(' ');
sb.append(alias);
}
return sb.toString();
}
public String getDomainObjectName() {
return stringValueOrElseGet(configuredDomainObjectName, this::calculateDomainObjectName);
}
private String calculateDomainObjectName() {
String finalDomainObjectName = mapStringValueOrElseGet(runtimeTableName,
s -> JavaBeansUtil.getCamelCaseString(s, true),
() -> JavaBeansUtil.getCamelCaseString(introspectedTableName, true));
if (domainObjectRenamingRule != null) {
Pattern pattern = domainObjectRenamingRule.pattern();
String replaceString = domainObjectRenamingRule.replaceString();
Matcher matcher = pattern.matcher(finalDomainObjectName);
finalDomainObjectName = JavaBeansUtil.getFirstCharacterUppercase(matcher.replaceAll(replaceString));
}
return finalDomainObjectName;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FullyQualifiedTable other)) {
return false;
}
return Objects.equals(this.introspectedTableName, other.introspectedTableName)
&& Objects.equals(this.introspectedCatalog, other.introspectedCatalog)
&& Objects.equals(this.introspectedSchema, other.introspectedSchema);
}
@Override
public int hashCode() {
return Objects.hash(introspectedTableName, introspectedCatalog, introspectedCatalog);
}
@Override
public String toString() {
return composeFullyQualifiedTableName(
introspectedCatalog, introspectedSchema, introspectedTableName,
'.');
}
public Optional<String> getAlias() {
return Optional.ofNullable(alias);
}
/**
* Calculates a Java package fragment based on the table catalog and schema.
* If qualifiers are ignored, then this method will return an empty string.
*
* <p>This method is used for determining the sub package for Java client and
* SQL map (XML) objects. It ignores any sub-package added to the
* domain object name in the table configuration.
*
* @param isSubPackagesEnabled
* the is sub packages enabled
* @return the subpackage for this table
*/
public String getSubPackageForClientOrSqlMap(boolean isSubPackagesEnabled) {
StringBuilder sb = new StringBuilder();
if (!ignoreQualifiersAtRuntime && isSubPackagesEnabled) {
if (stringHasValue(runtimeCatalog)) {
sb.append('.');
sb.append(runtimeCatalog.toLowerCase());
} else if (stringHasValue(introspectedCatalog)) {
sb.append('.');
sb.append(introspectedCatalog.toLowerCase());
}
if (stringHasValue(runtimeSchema)) {
sb.append('.');
sb.append(runtimeSchema.toLowerCase());
} else if (stringHasValue(introspectedSchema)) {
sb.append('.');
sb.append(introspectedSchema.toLowerCase());
}
}
// TODO - strip characters that are not valid in package names
return sb.toString();
}
/**
* Calculates a Java package fragment based on the table catalog and schema.
* If qualifiers are ignored, then this method will return an empty string.
*
* <p>This method is used for determining the sub package for Java model objects only.
* It takes into account the possibility that a sub-package was added to the
* domain object name in the table configuration.
*
* @param isSubPackagesEnabled
* the is sub packages enabled
* @return the subpackage for this table
*/
public String getSubPackageForModel(boolean isSubPackagesEnabled) {
StringBuilder sb = new StringBuilder();
sb.append(getSubPackageForClientOrSqlMap(isSubPackagesEnabled));
if (stringHasValue(domainObjectSubPackage)) {
sb.append('.');
sb.append(domainObjectSubPackage);
}
return sb.toString();
}
private void addDelimiters(StringBuilder sb) {
if (stringHasValue(beginningDelimiter)) {
sb.insert(0, beginningDelimiter);
}
if (stringHasValue(endingDelimiter)) {
sb.append(endingDelimiter);
}
}
public Optional<String> getDomainObjectSubPackage() {
return Optional.ofNullable(domainObjectSubPackage);
}
public static class Builder {
private @Nullable String introspectedCatalog;
private @Nullable String introspectedSchema;
private @Nullable String introspectedTableName;
private @Nullable String runtimeCatalog;
private @Nullable String runtimeSchema;
private @Nullable String runtimeTableName;
private @Nullable String domainObjectName;
private @Nullable String alias;
private boolean ignoreQualifiersAtRuntime;
private boolean delimitIdentifiers;
private @Nullable DomainObjectRenamingRule domainObjectRenamingRule;
private @Nullable Context context;
/**
* Sets the actual catalog of the table as returned from DatabaseMetaData.
*
* <p>This value should only be set if the user configured a catalog. Otherwise, the
* DatabaseMetaData is reporting some database default that we don't want in the generated code.
*
* @param introspectedCatalog the introspected catalog
* @return this builder
*/
public Builder withIntrospectedCatalog(@Nullable String introspectedCatalog) {
this.introspectedCatalog = introspectedCatalog;
return this;
}
/**
* Sets the actual schema of the table as returned from DatabaseMetaData.
*
* <p>This value should only be set if the user configured a schema. Otherwise, the
* DatabaseMetaData is reporting some database default that we don't want in the generated code.
*
* @param introspectedSchema the introspected schema
* @return this builder
*/
public Builder withIntrospectedSchema(@Nullable String introspectedSchema) {
this.introspectedSchema = introspectedSchema;
return this;
}
/**
* Sets the actual table name as returned from DatabaseMetaData.
*
* @param introspectedTableName the introspected table name
* @return this builder
*/
public Builder withIntrospectedTableName(String introspectedTableName) {
this.introspectedTableName = introspectedTableName;
return this;
}
/**
* Sets the runtime catalog.
*
* <p>This is used to "rename" the catalog in the generated SQL. This is useful, for example, when
* generating code against one catalog that should run with a different catalog.
*
* @param runtimeCatalog the runtime catalog
* @return this builder
*/
public Builder withRuntimeCatalog(@Nullable String runtimeCatalog) {
this.runtimeCatalog = runtimeCatalog;
return this;
}
/**
* Sets the runtime schema.
*
* <p>This is used to "rename" the schema in the generated SQL. This is useful, for example, when
* generating code against one schema that should run with a different schema.
*
* @param runtimeSchema the runtime schema
* @return this builder
*/
public Builder withRuntimeSchema(@Nullable String runtimeSchema) {
this.runtimeSchema = runtimeSchema;
return this;
}
/**
* Sets the runtime table name.
*
* <p>This is used to "rename" the table in the generated SQL. This is useful, for example, when generating
* code to run with an Oracle synonym. The user would have to specify the actual table name and schema
* for generation, but would want to use the synonym name in the generated SQL
*
* @param runtimeTableName the runtime table name
* @return this builder
*/
public Builder withRuntimeTableName(@Nullable String runtimeTableName) {
this.runtimeTableName = runtimeTableName;
return this;
}
/**
* Set the configured domain object name for this table.
*
* <p>If nothing is configured, we'll build the domain object named based on the tableName or runtimeTableName.
*
* @param domainObjectName the domain object name
* @return this builder
*/
public Builder withDomainObjectName(@Nullable String domainObjectName) {
this.domainObjectName = domainObjectName;
return this;
}
/**
* Sets a configured alias for the table. This alias will be added to the table name in the SQL.
*
* @param alias the alias
* @return this builder
*/
public Builder withAlias(@Nullable String alias) {
this.alias = alias;
return this;
}
/**
* If true, then the catalog and schema qualifiers will be ignored when composing fully qualified names
* in the generated SQL.
*
* <p>This is used, for example, when the user needs to specify a specific schema for
* generating code but does not want the schema in the generated SQL
*
* @param ignoreQualifiersAtRuntime whether to ignore qualifiers at runtime
* @return this builder
*/
public Builder withIgnoreQualifiersAtRuntime(boolean ignoreQualifiersAtRuntime) {
this.ignoreQualifiersAtRuntime = ignoreQualifiersAtRuntime;
return this;
}
/**
* If true, then the table identifiers will be delimited at runtime.
*
* <p>The delimiter characters are obtained from the Context.
*
* @param delimitIdentifiers whether to delimit identifiers at runtime
* @return this builder
*/
public Builder withDelimitIdentifiers(boolean delimitIdentifiers) {
this.delimitIdentifiers = delimitIdentifiers;
return this;
}
/**
* Sets a domain object renaming rule.
*
* <p>This is ignored is a domain object name is configured.
*
* <p>If a domain object name is not configured, we'll build the domain object named based on the tableName
* or runtimeTableName, and then we use the domain object renaming rule to generate the final domain object
* name.
*
* @param domainObjectRenamingRule the domain object renaming rule
* @return this builder
*/
public Builder withDomainObjectRenamingRule(@Nullable DomainObjectRenamingRule domainObjectRenamingRule) {
this.domainObjectRenamingRule = domainObjectRenamingRule;
return this;
}
public Builder withContext(Context context) {
this.context = context;
return this;
}
public FullyQualifiedTable build() {
return new FullyQualifiedTable(this);
}
}
}