1   package com.atlassian.plugins.codegen.modules;
2   
3   import java.util.Map;
4   
5   import com.atlassian.plugins.codegen.ClassId;
6   import com.atlassian.plugins.codegen.PluginProjectChangeset;
7   import com.atlassian.plugins.codegen.modules.common.Resource;
8   import com.atlassian.plugins.codegen.util.CodeTemplateHelper;
9   
10  import com.google.common.collect.ImmutableMap;
11  
12  import org.apache.commons.io.FilenameUtils;
13  
14  import static com.atlassian.plugins.codegen.ModuleDescriptor.moduleDescriptor;
15  import static com.atlassian.plugins.codegen.ResourceFile.resourceFile;
16  import static com.atlassian.plugins.codegen.SourceFile.sourceFile;
17  import static com.atlassian.plugins.codegen.SourceFile.SourceGroup.MAIN;
18  import static com.atlassian.plugins.codegen.SourceFile.SourceGroup.TESTS;
19  
20  /**
21   * Abstract base class for implementations of {@link PluginModuleCreator} that provides some
22   * helper methods for commonly used project modifications.
23   */
24  public abstract class AbstractPluginModuleCreator<T extends PluginModuleProperties> implements PluginModuleCreator<T>
25  {
26      public static final String DEFAULT_I18N_NAME = "atlassian-plugin";
27      public static final String FUNC_TEST_PACKAGE = "it";
28      public static final String TEST_SUFFIX = "Test";
29      public static final String FUNCT_TEST_SUFFIX = "FuncTest";
30      public static final String GENERIC_TEMPLATE_PREFIX = "templates/generic/";
31      public static final String GENERIC_TEST_TEMPLATE = GENERIC_TEMPLATE_PREFIX + "GenericTest.java.vtl";
32      public static final String TEMPLATES = "templates/";
33      
34      protected CodeTemplateHelper templateHelper;
35  
36      protected AbstractPluginModuleCreator()
37      {
38          this(new CodeTemplateHelper());
39      }
40  
41      protected AbstractPluginModuleCreator(CodeTemplateHelper templateHelper)
42      {
43          this.templateHelper = templateHelper;
44      }
45  
46      @Override
47      public abstract PluginProjectChangeset createModule(T props) throws Exception;
48  
49      /**
50       * Returns a changeset that will add a source file to the project, within the main source directory.
51       * @param props  property set whose {@link ClassBasedModuleProperties#getClassId()} method provides the class name;
52       *   other properties may be used in the template
53       * @param templateName  path to the template file for creating the source code
54       * @return  a {@link PluginProjectChangeset} that describes the new file
55       */
56      protected PluginProjectChangeset createClass(ClassBasedModuleProperties props, String templateName) throws Exception
57      {
58          return createClass(props, props.getClassId(), templateName);
59      }
60      
61      /**
62       * Returns a changeset that will add a source file to the project, within the main source directory.
63       * @param props  property set whose properties may be used in the template
64       * @param classId  describes the class and package name
65       * @param templateName  path to the template file for creating the source code
66       * @return  a {@link PluginProjectChangeset} that describes the new file
67       */
68      protected PluginProjectChangeset createClass(ClassBasedModuleProperties props, ClassId classId, String templateName) throws Exception
69      {
70          return new PluginProjectChangeset().withSourceFile(sourceFile(classId, MAIN, fromTemplate(templateName, props)));
71      }
72  
73      /**
74       * Returns a changeset that will add a source file to the project, within the test source directory.
75       * @param props  property set whose properties may be used in the template
76       * @param classId  describes the class and package name
77       * @param templateName  path to the template file for creating the source code
78       * @return  a {@link PluginProjectChangeset} that describes the new file
79       */
80      protected PluginProjectChangeset createTestClass(ClassBasedModuleProperties props, ClassId classId, String templateName) throws Exception
81      {
82          return new PluginProjectChangeset().withSourceFile(sourceFile(classId, TESTS, fromTemplate(templateName, props)));
83      }
84  
85      /**
86       * Returns a changeset that will add a source file and also its corresponding unit test.
87       * @param props  property set whose properties may be used in the template; the source file will be
88       *   for the class described by {@link ClassBasedModuleProperties#getClassId()} and the unit test
89       *   class will be determined by {@link #testClassFor(ClassId)}
90       * @param mainTemplate  path to the template file for creating the main source code
91       * @param unitTestTemplate  path to the template file for creating the unit test source code
92       * @return  a {@link PluginProjectChangeset} that describes the new files
93       */
94      protected PluginProjectChangeset createClassAndTests(ClassBasedModuleProperties props,
95                                                           String mainTemplate,
96                                                           String unitTestTemplate) throws Exception
97      {
98          return new PluginProjectChangeset()
99              .with(createClass(props, mainTemplate))
100             .with(createTestClass(props, testClassFor(props.getClassId()), unitTestTemplate));
101     }
102     
103     /**
104      * Returns a changeset that will add a source file and also its corresponding unit test and
105      * functional test.
106      * @param props  property set whose properties may be used in the template; the source file will be
107      *   for the class described by {@link ClassBasedModuleProperties#getClassId()}, the unit test
108      *   class will be determined by {@link #testClassFor(ClassId)}, and the functional test class
109      *   will be determined by {@link #funcTestClassFor(ClassId)}
110      * @param mainTemplate  path to the template file for creating the main source code
111      * @param unitTestTemplate  path to the template file for creating the unit test source code
112      * @param unitTestTemplate  path to the template file for creating the functional test source code
113      * @return  a {@link PluginProjectChangeset} that describes the new files
114      */
115     protected PluginProjectChangeset createClassAndTests(ClassBasedModuleProperties props,
116                                                          String mainTemplate,
117                                                          String unitTestTemplate,
118                                                          String funcTestTemplate) throws Exception
119     {
120         return createClassAndTests(props, mainTemplate, unitTestTemplate)
121             .with(createTestClass(props, funcTestClassFor(props.getClassId()), funcTestTemplate));
122     }
123     
124     /**
125      * Returns a changeset that will add a plugin module to the project's plugin XML file.  Also
126      * adds any i18n properties that are required for the module.
127      * @param props  property set whose properties may be used in the template; also may provide
128      *   i18n properties with {@link PluginModuleProperties#getI18nProperties()}
129      * @param templateName  path to the template file for the module's XML fragment
130      * @return  a {@link PluginProjectChangeset} that describes the new module and i18n changes
131      */
132     protected PluginProjectChangeset createModule(PluginModuleProperties props, String templateName) throws Exception
133     {
134         return new PluginProjectChangeset().withModuleDescriptor(moduleDescriptor(fromTemplate(templateName, props)))
135             .withI18nProperties(props.getI18nProperties());
136     }
137     
138     /**
139      * Returns a changeset that will add a resource file to the project, within the main resource
140      * directory or a subdirectory thereof.
141      * @param props  property set whose properties may be used in the template
142      * @param path  relative path within the resource directory, or "" for no subpath
143      * @param fileName  base name for the new file
144      * @param templateName  path to the template file for the resource content
145      * @return  a {@link PluginProjectChangeset} that describes the new file
146      */
147     protected PluginProjectChangeset createResource(Map<Object, Object> props, String path, String fileName, String templateName) throws Exception
148     {
149         return new PluginProjectChangeset().withResourceFile(resourceFile(path, fileName, fromTemplate(templateName, props)));
150     }
151 
152     /**
153      * Returns a changeset that will add a resource file to the project, within a subdirectory of
154      * the main resource directory that is prefixed with "templates/".
155      * @param props  property set whose properties may be used in the template
156      * @param path  relative path within the resource directory, or "" for no subpath
157      * @param fileName  base name for the new file
158      * @param templateName  path to the template file for the resource content
159      * @return  a {@link PluginProjectChangeset} that describes the new file
160      */
161     protected PluginProjectChangeset createTemplateResource(Map<Object, Object> props, String path, String fileName, String templateName) throws Exception
162     {
163         path = path.equals("") ? TEMPLATES : (path.startsWith(TEMPLATES) ? path : (TEMPLATES + path));
164         return new PluginProjectChangeset().withResourceFile(resourceFile(path, fileName, fromTemplate(templateName, props)));
165     }
166 
167     /**
168      * Wrapper for {@link #createTemplateResource(Map, Resource, String)} that derives the path
169      * and filename from a {@link Resource} object.  Also sets the property "CURRENT_VIEW" to the
170      * filename when generating the template.
171      * @param props  property set whose properties may be used in the template
172      * @param resource  a {@link Resource} describing the path and filename
173      * @param templateName  path to the template file for the resource content
174      * @return  a {@link PluginProjectChangeset} that describes the new file
175      */
176     protected PluginProjectChangeset createTemplateResource(Map<Object, Object> props, Resource resource, String templateName) throws Exception
177     {
178         String resourceFullPath = FilenameUtils.separatorsToSystem(resource.getLocation());
179         String path = FilenameUtils.getPath(resourceFullPath);
180         String fileName = FilenameUtils.getName(resourceFullPath);
181         Map<Object, Object> tempProps = ImmutableMap.builder().putAll(props).put("CURRENT_VIEW", fileName).build();
182         return createTemplateResource(tempProps, path, fileName, templateName);
183     }
184     
185     /**
186      * Generates content using a template file.
187      * @param templatePath  path to the template file
188      * @param props  properties that may be used in the template
189      * @return
190      * @throws Exception
191      */
192     protected String fromTemplate(String templatePath, Map<Object, Object> props) throws Exception
193     {
194         return templateHelper.getStringFromTemplate(templatePath, props);
195     }
196     
197     /**
198      * Returns the standard unit test class corresponding to the given class.  The test class
199      * is in the same package but has {@link #TEST_SUFFIX} appended to its name.
200      */
201     protected ClassId testClassFor(ClassId mainClass)
202     {
203         return mainClass.classNameSuffix(TEST_SUFFIX);
204     }
205     
206     /**
207      * Returns the standard functional test class corresponding to the given class.  The test
208      * class has {@link #FUNCT_TEST_SUFFIX} appended to its name and {@link #FUNC_TEST_PACKAGE}
209      * prepended to its package.
210      */
211     protected ClassId funcTestClassFor(ClassId mainClass)
212     {
213         return mainClass.packageNamePrefix(FUNC_TEST_PACKAGE).classNameSuffix(FUNCT_TEST_SUFFIX);
214     }
215 }