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