View Javadoc
1   /**
2    *    Copyright 2006-2015 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    *       http://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 org.mybatis.generator.config.Configuration;
19  import org.mybatis.generator.config.Context;
20  import org.mybatis.generator.config.MergeConstants;
21  import org.mybatis.generator.exception.InvalidConfigurationException;
22  import org.mybatis.generator.exception.ShellException;
23  import org.mybatis.generator.internal.DefaultShellCallback;
24  import org.mybatis.generator.internal.NullProgressCallback;
25  import org.mybatis.generator.internal.ObjectFactory;
26  import org.mybatis.generator.internal.XmlFileMergerJaxp;
27  
28  import java.io.*;
29  import java.sql.SQLException;
30  import java.util.ArrayList;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Set;
34  
35  import static org.mybatis.generator.internal.util.ClassloaderUtility.getCustomClassloader;
36  import static org.mybatis.generator.internal.util.messages.Messages.getString;
37  
38  /**
39   * This class is the main interface to MyBatis generator. A typical execution of
40   * the tool involves these steps:
41   * 
42   * <ol>
43   * <li>Create a Configuration object. The Configuration can be the result of a
44   * parsing the XML configuration file, or it can be created solely in Java.</li>
45   * <li>Create a MyBatisGenerator object</li>
46   * <li>Call one of the generate() methods</li>
47   * </ol>
48   * 
49   * @see org.mybatis.generator.config.xml.ConfigurationParser
50   * 
51   * @author Jeff Butler
52   */
53  public class MyBatisGenerator {
54  
55      private Configuration configuration;
56  
57      private ShellCallback shellCallback;
58  
59      private List<GeneratedJavaFile> generatedJavaFiles;
60  
61      private List<GeneratedExtjsFile> generatedExtjsFiles;
62  
63      private List<GeneratedXmlFile> generatedXmlFiles;
64  
65      private List<String> warnings;
66  
67      private Set<String> projects;
68  
69      /**
70       * Constructs a MyBatisGenerator object.
71       * 
72       * @param configuration
73       *            The configuration for this invocation
74       * @param shellCallback
75       *            an instance of a ShellCallback interface. You may specify
76       *            <code>null</code> in which case the DefaultShellCallback will
77       *            be used.
78       * @param warnings
79       *            Any warnings generated during execution will be added to this
80       *            list. Warnings do not affect the running of the tool, but they
81       *            may affect the results. A typical warning is an unsupported
82       *            data type. In that case, the column will be ignored and
83       *            generation will continue. You may specify <code>null</code> if
84       *            you do not want warnings returned.
85       * @throws InvalidConfigurationException
86       *             if the specified configuration is invalid
87       */
88      public MyBatisGenerator(Configuration configuration, ShellCallback shellCallback,
89              List<String> warnings) throws InvalidConfigurationException {
90          super();
91          if (configuration == null) {
92              throw new IllegalArgumentException(getString("RuntimeError.2")); //$NON-NLS-1$
93          } else {
94              this.configuration = configuration;
95          }
96  
97          if (shellCallback == null) {
98              this.shellCallback = new DefaultShellCallback(false);
99          } else {
100             this.shellCallback = shellCallback;
101         }
102 
103         if (warnings == null) {
104             this.warnings = new ArrayList<String>();
105         } else {
106             this.warnings = warnings;
107         }
108         generatedJavaFiles = new ArrayList<GeneratedJavaFile>();
109         generatedExtjsFiles = new ArrayList<GeneratedExtjsFile>();
110         generatedXmlFiles = new ArrayList<GeneratedXmlFile>();
111         projects = new HashSet<String>();
112 
113         this.configuration.validate();
114     }
115 
116     /**
117      * This is the main method for generating code. This method is long running,
118      * but progress can be provided and the method can be canceled through the
119      * ProgressCallback interface. This version of the method runs all
120      * configured contexts.
121      * 
122      * @param callback
123      *            an instance of the ProgressCallback interface, or
124      *            <code>null</code> if you do not require progress information
125      * @throws SQLException
126      * @throws IOException
127      * @throws InterruptedException
128      *             if the method is canceled through the ProgressCallback
129      */
130     public void generate(ProgressCallback callback) throws SQLException,
131             IOException, InterruptedException {
132         generate(callback, null, null);
133     }
134 
135     /**
136      * This is the main method for generating code. This method is long running,
137      * but progress can be provided and the method can be canceled through the
138      * ProgressCallback interface.
139      * 
140      * @param callback
141      *            an instance of the ProgressCallback interface, or
142      *            <code>null</code> if you do not require progress information
143      * @param contextIds
144      *            a set of Strings containing context ids to run. Only the
145      *            contexts with an id specified in this list will be run. If the
146      *            list is null or empty, than all contexts are run.
147      * @throws InvalidConfigurationException
148      * @throws SQLException
149      * @throws IOException
150      * @throws InterruptedException
151      *             if the method is canceled through the ProgressCallback
152      */
153     public void generate(ProgressCallback callback, Set<String> contextIds)
154             throws SQLException, IOException, InterruptedException {
155         generate(callback, contextIds, null);
156     }
157 
158     /**
159      * This is the main method for generating code. This method is long running,
160      * but progress can be provided and the method can be cancelled through the
161      * ProgressCallback interface.
162      * 
163      * @param callback
164      *            an instance of the ProgressCallback interface, or
165      *            <code>null</code> if you do not require progress information
166      * @param contextIds
167      *            a set of Strings containing context ids to run. Only the
168      *            contexts with an id specified in this list will be run. If the
169      *            list is null or empty, than all contexts are run.
170      * @param fullyQualifiedTableNames
171      *            a set of table names to generate. The elements of the set must
172      *            be Strings that exactly match what's specified in the
173      *            configuration. For example, if table name = "foo" and schema =
174      *            "bar", then the fully qualified table name is "foo.bar". If
175      *            the Set is null or empty, then all tables in the configuration
176      *            will be used for code generation.
177      * @throws InvalidConfigurationException
178      * @throws SQLException
179      * @throws IOException
180      * @throws InterruptedException
181      *             if the method is canceled through the ProgressCallback
182      */
183     public void generate(ProgressCallback callback, Set<String> contextIds,
184             Set<String> fullyQualifiedTableNames) throws SQLException,
185             IOException, InterruptedException {
186 
187         if (callback == null) {
188             callback = new NullProgressCallback();
189         }
190 
191         generatedJavaFiles.clear();
192         generatedExtjsFiles.clear();
193         generatedXmlFiles.clear();
194 
195         // calculate the contexts to run
196         List<Context> contextsToRun;
197         if (contextIds == null || contextIds.size() == 0) {
198             contextsToRun = configuration.getContexts();
199         } else {
200             contextsToRun = new ArrayList<Context>();
201             for (Context context : configuration.getContexts()) {
202                 if (contextIds.contains(context.getId())) {
203                     contextsToRun.add(context);
204                 }
205             }
206         }
207 
208         // setup custom classloader if required
209         if (configuration.getClassPathEntries().size() > 0) {
210             ClassLoader classLoader = getCustomClassloader(configuration.getClassPathEntries());
211             ObjectFactory.addExternalClassLoader(classLoader);
212         }
213 
214         // now run the introspections...
215         int totalSteps = 0;
216         for (Context context : contextsToRun) {
217             totalSteps += context.getIntrospectionSteps();
218         }
219         callback.introspectionStarted(totalSteps);
220 
221         for (Context context : contextsToRun) {
222             context.introspectTables(callback, warnings,
223                     fullyQualifiedTableNames);
224         }
225 
226         // now run the generates
227         totalSteps = 0;
228         for (Context context : contextsToRun) {
229             totalSteps += context.getGenerationSteps();
230         }
231         callback.generationStarted(totalSteps);
232 
233         for (Context context : contextsToRun) {
234             context.generateFiles(callback, generatedJavaFiles,
235                     generatedExtjsFiles,
236                     generatedXmlFiles, warnings);
237         }
238 
239         // now save the files
240         callback.saveStarted(generatedXmlFiles.size()
241                 + generatedExtjsFiles.size()
242                 + generatedJavaFiles.size());
243 
244         for (GeneratedXmlFile gxf : generatedXmlFiles) {
245             projects.add(gxf.getTargetProject());
246 
247             File targetFile;
248             String source;
249             try {
250                 File directory = shellCallback.getDirectory(gxf
251                         .getTargetProject(), gxf.getTargetPackage());
252                 targetFile = new File(directory, gxf.getFileName());
253                 if (targetFile.exists()) {
254                     if (gxf.isMergeable()) {
255                         source = XmlFileMergerJaxp.getMergedSource(gxf,
256                                 targetFile);
257                     } else if (shellCallback.isOverwriteEnabled()) {
258                         source = gxf.getFormattedContent();
259                         warnings.add(getString("Warning.11", //$NON-NLS-1$
260                                 targetFile.getAbsolutePath()));
261                     } else {
262                         source = gxf.getFormattedContent();
263                         targetFile = getUniqueFileName(directory, gxf
264                                 .getFileName());
265                         warnings.add(getString(
266                                 "Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
267                     }
268                 } else {
269                     source = gxf.getFormattedContent();
270                 }
271             } catch (ShellException e) {
272                 warnings.add(e.getMessage());
273                 continue;
274             }
275 
276             callback.checkCancel();
277             callback.startTask(getString(
278                     "Progress.15", targetFile.getName())); //$NON-NLS-1$
279             writeFile(targetFile, source, "UTF-8"); //$NON-NLS-1$
280         }
281 
282         for (GeneratedJavaFile gjf : generatedJavaFiles) {
283             projects.add(gjf.getTargetProject());
284 
285             File targetFile;
286             String source;
287             try {
288                 File directory = shellCallback.getDirectory(gjf
289                         .getTargetProject(), gjf.getTargetPackage());
290                 targetFile = new File(directory, gjf.getFileName());
291                 if (targetFile.exists()) {
292                     if (shellCallback.isMergeSupported()) {
293                         source = shellCallback.mergeJavaFile(gjf
294                                 .getFormattedContent(), targetFile
295                                 .getAbsolutePath(),
296                                 MergeConstants.OLD_ELEMENT_TAGS,
297                                 gjf.getFileEncoding());
298                     } else if (shellCallback.isOverwriteEnabled()) {
299                         source = gjf.getFormattedContent();
300                         warnings.add(getString("Warning.11", //$NON-NLS-1$
301                                 targetFile.getAbsolutePath()));
302                     } else {
303                         source = gjf.getFormattedContent();
304                         targetFile = getUniqueFileName(directory, gjf
305                                 .getFileName());
306                         warnings.add(getString(
307                                 "Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
308                     }
309                 } else {
310                     source = gjf.getFormattedContent();
311                 }
312 
313                 callback.checkCancel();
314                 callback.startTask(getString(
315                         "Progress.15", targetFile.getName())); //$NON-NLS-1$
316                 writeFile(targetFile, source, gjf.getFileEncoding());
317             } catch (ShellException e) {
318                 warnings.add(e.getMessage());
319             }
320         }
321 
322 
323 
324         for (GeneratedExtjsFile extf : generatedExtjsFiles) {
325             projects.add(extf.getTargetProject());
326 
327             File targetFile;
328             String source;
329             try {
330                 File directory = shellCallback.getDirectory(extf
331                         .getTargetProject(), extf.getTargetPackage());
332                 targetFile = new File(directory, extf.getFileName());
333                 if (targetFile.exists()) {
334                     if (shellCallback.isMergeSupported()) {
335                         // TODO 未处理合并
336                         source = extf.getFormattedContent();
337                     } else if (shellCallback.isOverwriteEnabled()) {
338                         source = extf.getFormattedContent();
339                         warnings.add(getString("Warning.11", //$NON-NLS-1$
340                                 targetFile.getAbsolutePath()));
341                     } else {
342                         source = extf.getFormattedContent();
343                         targetFile = getUniqueFileName(directory, extf
344                                 .getFileName());
345                         warnings.add(getString(
346                                 "Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
347                     }
348                 } else {
349                     source = extf.getFormattedContent();
350                 }
351 
352                 callback.checkCancel();
353                 callback.startTask(getString(
354                         "Progress.15", targetFile.getName())); //$NON-NLS-1$
355                 writeFile(targetFile, source, extf.getFileEncoding());
356             } catch (ShellException e) {
357                 warnings.add(e.getMessage());
358             }
359         }
360 
361         for (String project : projects) {
362             shellCallback.refreshProject(project);
363         }
364 
365         callback.done();
366     }
367 
368     /**
369      * Writes, or overwrites, the contents of the specified file
370      * 
371      * @param file
372      * @param content
373      */
374     private void writeFile(File file, String content, String fileEncoding) throws IOException {
375         FileOutputStream fos = new FileOutputStream(file, false);
376         OutputStreamWriter osw;
377         if (fileEncoding == null) {
378             osw = new OutputStreamWriter(fos);
379         } else {
380             osw = new OutputStreamWriter(fos, fileEncoding);
381         }
382         
383         BufferedWriter bw = new BufferedWriter(osw);
384         bw.write(content);
385         bw.close();
386     }
387 
388     private File getUniqueFileName(File directory, String fileName) {
389         File answer = null;
390 
391         // try up to 1000 times to generate a unique file name
392         StringBuilder sb = new StringBuilder();
393         for (int i = 1; i < 1000; i++) {
394             sb.setLength(0);
395             sb.append(fileName);
396             sb.append('.');
397             sb.append(i);
398 
399             File testFile = new File(directory, sb.toString());
400             if (!testFile.exists()) {
401                 answer = testFile;
402                 break;
403             }
404         }
405 
406         if (answer == null) {
407             throw new RuntimeException(getString(
408                     "RuntimeError.3", directory.getAbsolutePath())); //$NON-NLS-1$
409         }
410 
411         return answer;
412     }
413 }