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