View Javadoc
1   /*
2    *    Copyright 2006-2026 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.generator.api;
17  
18  import java.util.ArrayList;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Objects;
22  import java.util.Optional;
23  import java.util.function.Function;
24  import java.util.stream.Stream;
25  
26  import org.jspecify.annotations.Nullable;
27  import org.mybatis.generator.internal.PluginAggregator;
28  import org.mybatis.generator.internal.rules.ConditionalModelRules;
29  import org.mybatis.generator.internal.rules.FlatModelRules;
30  import org.mybatis.generator.internal.rules.HierarchicalModelRules;
31  import org.mybatis.generator.internal.rules.Rules;
32  
33  /**
34   * This class holds the results of database introspection - namely the different columns associated with the table.
35   * The class is a subclass of CodeGenerationAttributes for purposes of preserving compatibility with existing plugin
36   * methods.
37   *
38   * @author Jeff Butler
39   */
40  public class IntrospectedTable extends CodeGenerationAttributes {
41      protected final List<IntrospectedColumn> primaryKeyColumns = new ArrayList<>();
42      protected final List<IntrospectedColumn> baseColumns = new ArrayList<>();
43      protected final List<IntrospectedColumn> blobColumns = new ArrayList<>();
44  
45      /**
46       * Table remarks retrieved from database metadata.
47       */
48      protected @Nullable String remarks;
49  
50      /**
51       * Table type retrieved from database metadata.
52       *
53       * <p>Non-null in practice
54       */
55      protected @Nullable String tableType;
56  
57      protected IntrospectedTable(Builder builder) {
58          super(builder);
59          Objects.requireNonNull(builder.pluginAggregator);
60          builder.pluginAggregator.initialized(this);
61      }
62  
63      public Optional<IntrospectedColumn> getColumn(String columnName) {
64          return Stream.of(primaryKeyColumns.stream(), baseColumns.stream(), blobColumns.stream())
65                  .flatMap(Function.identity())
66                  .filter(ic -> columnMatches(ic, columnName))
67                  .findFirst();
68      }
69  
70      private boolean columnMatches(IntrospectedColumn introspectedColumn, String columnName) {
71          if (introspectedColumn.isColumnNameDelimited()) {
72              return introspectedColumn.getActualColumnName().equals(columnName);
73          } else {
74              return introspectedColumn.getActualColumnName().equalsIgnoreCase(columnName);
75          }
76      }
77  
78      /**
79       * Returns true if any of the columns in the table are JDBC Dates (as
80       * opposed to timestamps).
81       *
82       * @return true if the table contains DATE columns
83       */
84      public boolean hasJDBCDateColumns() {
85          return Stream.of(primaryKeyColumns.stream(), baseColumns.stream())
86                  .flatMap(Function.identity())
87                  .anyMatch(IntrospectedColumn::isJDBCDateColumn);
88      }
89  
90      /**
91       * Returns true if any of the columns in the table are JDBC Times (as
92       * opposed to timestamps).
93       *
94       * @return true if the table contains TIME columns
95       */
96      public boolean hasJDBCTimeColumns() {
97          return Stream.of(primaryKeyColumns.stream(), baseColumns.stream())
98                  .flatMap(Function.identity())
99                  .anyMatch(IntrospectedColumn::isJDBCTimeColumn);
100     }
101 
102     /**
103      * Returns the columns in the primary key. If the generatePrimaryKeyClass()
104      * method returns false, then these columns will be iterated as the
105      * parameters of the selectByPrimaryKay and deleteByPrimaryKey methods
106      *
107      * @return a List of ColumnDefinition objects for columns in the primary key
108      */
109     public List<IntrospectedColumn> getPrimaryKeyColumns() {
110         return primaryKeyColumns;
111     }
112 
113     public boolean hasPrimaryKeyColumns() {
114         return !primaryKeyColumns.isEmpty();
115     }
116 
117     public List<IntrospectedColumn> getBaseColumns() {
118         return baseColumns;
119     }
120 
121     /**
122      * Returns all columns in the table (for use by the select by primary key and
123      * select by example with BLOBs methods).
124      *
125      * @return a List of ColumnDefinition objects for all columns in the table
126      */
127     public List<IntrospectedColumn> getAllColumns() {
128         return Stream.of(primaryKeyColumns.stream(), baseColumns.stream(), blobColumns.stream())
129                 .flatMap(Function.identity())
130                 .toList();
131     }
132 
133     /**
134      * Returns all columns except BLOBs (for use by the select by example without BLOBs method).
135      *
136      * @return a List of ColumnDefinition objects for columns in the table that are non BLOBs
137      */
138     public List<IntrospectedColumn> getNonBLOBColumns() {
139         return Stream.of(primaryKeyColumns.stream(), baseColumns.stream())
140                 .flatMap(Function.identity())
141                 .toList();
142     }
143 
144     public int getColumnCount() {
145         return primaryKeyColumns.size() + baseColumns.size() + blobColumns.size();
146     }
147 
148     public int getNonBLOBColumnCount() {
149         return primaryKeyColumns.size() + baseColumns.size();
150     }
151 
152     public List<IntrospectedColumn> getNonPrimaryKeyColumns() {
153         return Stream.of(baseColumns.stream(), blobColumns.stream())
154                 .flatMap(Function.identity())
155                 .toList();
156     }
157 
158     public List<IntrospectedColumn> getBLOBColumns() {
159         return blobColumns;
160     }
161 
162     public boolean hasBLOBColumns() {
163         return !blobColumns.isEmpty();
164     }
165 
166     public boolean hasBaseColumns() {
167         return !baseColumns.isEmpty();
168     }
169 
170     public boolean hasAnyColumns() {
171         return hasPrimaryKeyColumns() || hasBaseColumns() || hasBLOBColumns();
172     }
173 
174     public void addColumn(IntrospectedColumn introspectedColumn) {
175         if (introspectedColumn.isBLOBColumn()) {
176             blobColumns.add(introspectedColumn);
177         } else {
178             baseColumns.add(introspectedColumn);
179         }
180 
181         introspectedColumn.setIntrospectedTable(this);
182     }
183 
184     public void addPrimaryKeyColumn(String columnName) {
185         boolean found = false;
186         // first search base columns
187         Iterator<IntrospectedColumn> iter = baseColumns.iterator();
188         while (iter.hasNext()) {
189             IntrospectedColumn introspectedColumn = iter.next();
190             if (introspectedColumn.getActualColumnName().equals(columnName)) {
191                 primaryKeyColumns.add(introspectedColumn);
192                 iter.remove();
193                 found = true;
194                 break;
195             }
196         }
197 
198         // search blob columns in the weird event that a blob is the primary key
199         if (!found) {
200             iter = blobColumns.iterator();
201             while (iter.hasNext()) {
202                 IntrospectedColumn introspectedColumn = iter.next();
203                 if (introspectedColumn.getActualColumnName().equals(columnName)) {
204                     primaryKeyColumns.add(introspectedColumn);
205                     iter.remove();
206                     break;
207                 }
208             }
209         }
210     }
211 
212     public Optional<String> getRemarks() {
213         return Optional.ofNullable(remarks);
214     }
215 
216     public void setRemarks(String remarks) {
217         this.remarks = remarks;
218     }
219 
220     public String getTableType() {
221         return Objects.requireNonNull(tableType);
222     }
223 
224     public void setTableType(String tableType) {
225         this.tableType = tableType;
226     }
227 
228     @Override
229     protected Rules calculateRules() {
230         if (knownRuntime.isDynamicSqlBased() || knownRuntime == KnownRuntime.MYBATIS3_SIMPLE) {
231             return new FlatModelRules(this);
232         }
233 
234         return switch (getModelType()) {
235         case HIERARCHICAL -> new HierarchicalModelRules(this);
236         case FLAT, RECORD -> new FlatModelRules(this);
237         case CONDITIONAL -> new ConditionalModelRules(this);
238         };
239     }
240 
241     public static class Builder extends AbstractBuilder<Builder> {
242         private @Nullable PluginAggregator pluginAggregator;
243 
244         public Builder withPluginAggregator(PluginAggregator pluginAggregator) {
245             this.pluginAggregator = pluginAggregator;
246             return this;
247         }
248 
249         public IntrospectedTable build() {
250             return new IntrospectedTable(this);
251         }
252 
253         @Override
254         protected Builder getThis() {
255             return this;
256         }
257     }
258 }