Skip to content

Commit e266610

Browse files
committed
generic support for javaClientGenerator
1 parent b3d2c3d commit e266610

File tree

6 files changed

+130
-5
lines changed

6 files changed

+130
-5
lines changed

core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/AbstractJavaClientGenerator.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.mybatis.generator.codegen;
1717

18+
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
19+
1820
/**
1921
* This class exists to that Java client generators can specify whether
2022
* an XML generator is required to match the methods in the
@@ -49,4 +51,92 @@ public boolean requiresXMLGenerator() {
4951
* XML is required by this generator
5052
*/
5153
public abstract AbstractXmlGenerator getMatchedXMLGenerator();
54+
55+
/**
56+
* Processes the rootInterface string to replace generic type placeholders with the actual record type.
57+
* <p>
58+
* If the rootInterface contains a generic placeholder like {@code <T>}, {@code <E>}, etc., it will be
59+
* replaced with the actual record type from the introspected table. For example:
60+
* <ul>
61+
* <li>{@code "BaseMapper<T>"} becomes {@code "BaseMapper<com.example.User>"}</li>
62+
* <li>{@code "BaseMapper"} remains {@code "BaseMapper"} (no change for backward compatibility)</li>
63+
* <li>{@code "BaseMapper<com.example.User>"} remains unchanged (already fully qualified)</li>
64+
* </ul>
65+
*
66+
* @param rootInterface
67+
* the rootInterface string from configuration, may contain generic placeholders
68+
* @param recordType
69+
* the actual record type (fully qualified) to use as replacement
70+
* @return the processed rootInterface string with placeholders replaced, or the original string if no
71+
* replacement is needed
72+
*/
73+
protected String processRootInterfaceWithGenerics(String rootInterface, String recordType) {
74+
if (!stringHasValue(rootInterface) || !stringHasValue(recordType)) {
75+
return rootInterface;
76+
}
77+
78+
// Check if rootInterface contains generic brackets
79+
int openBracketIndex = rootInterface.indexOf('<');
80+
if (openBracketIndex == -1) {
81+
// No generic brackets, return as-is for backward compatibility
82+
return rootInterface;
83+
}
84+
85+
int closeBracketIndex = rootInterface.lastIndexOf('>');
86+
if (closeBracketIndex == -1 || closeBracketIndex <= openBracketIndex) {
87+
// Malformed generic brackets, return as-is
88+
return rootInterface;
89+
}
90+
91+
String baseInterface = rootInterface.substring(0, openBracketIndex);
92+
String genericContent = rootInterface.substring(openBracketIndex + 1, closeBracketIndex).trim();
93+
94+
// Check if the generic content is a placeholder (single letter or single letter with extends clause)
95+
// Common Java generic type parameter names: T, E, K, V, N, U, R, S
96+
if (isGenericPlaceholder(genericContent)) {
97+
// Replace placeholder with actual record type
98+
return baseInterface + "<" + recordType + ">";
99+
}
100+
101+
// Already has a concrete type (fully qualified or not), return as-is
102+
return rootInterface;
103+
}
104+
105+
/**
106+
* Checks if a generic content string represents a placeholder that should be replaced.
107+
* Placeholders are typically single-letter type parameters like T, E, K, V, etc.,
108+
* optionally with an "extends" clause like "T extends Entity".
109+
*
110+
* @param genericContent
111+
* the content between angle brackets
112+
* @return true if this looks like a placeholder that should be replaced
113+
*/
114+
private boolean isGenericPlaceholder(String genericContent) {
115+
if (genericContent.isEmpty()) {
116+
return false;
117+
}
118+
119+
String trimmed = genericContent.trim();
120+
121+
// Simple case: just a single uppercase letter (T, E, K, V, etc.)
122+
if (trimmed.length() == 1 && Character.isUpperCase(trimmed.charAt(0))) {
123+
return true;
124+
}
125+
126+
// Case with bounds: "T extends SomeClass" or "T super SomeClass"
127+
// Pattern: single uppercase letter followed by " extends " or " super "
128+
int extendsIndex = trimmed.indexOf(" extends ");
129+
int superIndex = trimmed.indexOf(" super ");
130+
131+
if (extendsIndex > 0 || superIndex > 0) {
132+
int boundIndex = extendsIndex > 0 ? extendsIndex : superIndex;
133+
// First part should be a single uppercase letter
134+
String typeParam = trimmed.substring(0, boundIndex).trim();
135+
if (typeParam.length() == 1 && Character.isUpperCase(typeParam.charAt(0))) {
136+
return true;
137+
}
138+
}
139+
140+
return false;
141+
}
52142
}

core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/JavaMapperGenerator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ public List<CompilationUnit> getCompilationUnits() {
7575
}
7676

7777
if (stringHasValue(rootInterface)) {
78-
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface);
78+
// Process rootInterface to replace generic placeholders with actual record type
79+
String recordType = introspectedTable.getBaseRecordType();
80+
String processedRootInterface = processRootInterfaceWithGenerics(rootInterface, recordType);
81+
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(processedRootInterface);
7982
interfaze.addSuperInterface(fqjt);
8083
interfaze.addImportedType(fqjt);
8184
}

core/mybatis-generator-core/src/main/java/org/mybatis/generator/codegen/mybatis3/javamapper/SimpleJavaClientGenerator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ public List<CompilationUnit> getCompilationUnits() {
6666
}
6767

6868
if (stringHasValue(rootInterface)) {
69-
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface);
69+
// Process rootInterface to replace generic placeholders with actual record type
70+
String recordType = introspectedTable.getBaseRecordType();
71+
String processedRootInterface = processRootInterfaceWithGenerics(rootInterface, recordType);
72+
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(processedRootInterface);
7073
interfaze.addSuperInterface(fqjt);
7174
interfaze.addImportedType(fqjt);
7275
}

core/mybatis-generator-core/src/main/java/org/mybatis/generator/runtime/dynamic/sql/DynamicSqlMapperGenerator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,10 @@ protected Interface createBasicInterface() {
155155
}
156156

157157
if (stringHasValue(rootInterface)) {
158-
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(rootInterface);
158+
// Process rootInterface to replace generic placeholders with actual record type
159+
String processedRootInterface = processRootInterfaceWithGenerics(rootInterface,
160+
recordType.getFullyQualifiedName());
161+
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(processedRootInterface);
159162
interfaze.addSuperInterface(fqjt);
160163
interfaze.addImportedType(fqjt);
161164
}

core/mybatis-generator-core/src/site/xhtml/configreference/javaClientGenerator.xhtml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,20 @@ specified with the <a href="property.html">&lt;property&gt;</a> child element:</
169169
<p><b>Important:</b> MBG does not verify that the interface exists, or is a
170170
valid Java interface.</p>
171171
<p>If specified, the value of this property should be a fully qualified
172-
interface name (like com.mycompany.MyRootInterface).</p></td>
172+
interface name (like com.mycompany.MyRootInterface).</p>
173+
<p><b>Generic Type Support:</b> If the root interface is a generic interface, you can
174+
use a placeholder for the type parameter. The placeholder will be automatically
175+
replaced with the actual record type for each table. For example:
176+
<ul>
177+
<li><code>com.mycompany.BaseMapper&lt;T&gt;</code> will become
178+
<code>com.mycompany.BaseMapper&lt;com.example.User&gt;</code> for a User table</li>
179+
<li><code>com.mycompany.BaseMapper&lt;T extends Entity&gt;</code> will become
180+
<code>com.mycompany.BaseMapper&lt;com.example.User&gt;</code></li>
181+
</ul>
182+
Placeholders are typically single uppercase letters (T, E, K, V, etc.) optionally
183+
followed by " extends ..." or " super ...". If you specify a fully qualified type
184+
(like <code>BaseMapper&lt;com.example.User&gt;</code>), it will be used as-is without
185+
replacement.</p></td>
173186
</tr>
174187
</table>
175188

core/mybatis-generator-core/src/site/xhtml/configreference/table.xhtml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,20 @@ specified with the <a href="property.html">&lt;property&gt;</a> child element:</
416416
<p><b>Important:</b> MBG does not verify that the interface exists, or is a
417417
valid Java interface.</p>
418418
<p>If specified, the value of this property should be a fully qualified
419-
interface name (like com.mycompany.MyRootInterface).</p></td>
419+
interface name (like com.mycompany.MyRootInterface).</p>
420+
<p><b>Generic Type Support:</b> If the root interface is a generic interface, you can
421+
use a placeholder for the type parameter. The placeholder will be automatically
422+
replaced with the actual record type for this table. For example:
423+
<ul>
424+
<li><code>com.mycompany.BaseMapper&lt;T&gt;</code> will become
425+
<code>com.mycompany.BaseMapper&lt;com.example.User&gt;</code></li>
426+
<li><code>com.mycompany.BaseMapper&lt;T extends Entity&gt;</code> will become
427+
<code>com.mycompany.BaseMapper&lt;com.example.User&gt;</code></li>
428+
</ul>
429+
Placeholders are typically single uppercase letters (T, E, K, V, etc.) optionally
430+
followed by " extends ..." or " super ...". If you specify a fully qualified type
431+
(like <code>BaseMapper&lt;com.example.User&gt;</code>), it will be used as-is without
432+
replacement.</p></td>
420433
</tr>
421434
<tr>
422435
<td valign="top">runtimeCatalog</td>

0 commit comments

Comments
 (0)