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 }