Source code: xdoclet/tagshandler/PackageTagsHandler.java
1 /*
2 * Copyright (c) 2001, 2002 The XDoclet team
3 * All rights reserved.
4 */
5 package xdoclet.tagshandler;
6
7 import java.io.Serializable;
8 import java.util.*;
9
10 import xjavadoc.*;
11
12 import xdoclet.DocletContext;
13 import xdoclet.XDocletException;
14 import xdoclet.XDocletMessages;
15 import xdoclet.template.TemplateException;
16 import xdoclet.util.Translator;
17
18 /**
19 * Tags which manipulate package names and packages, including substitutions.
20 *
21 * @author Ara Abrahamian (ara_e@email.com)
22 * @created Oct 14, 2001
23 * @xdoclet.taghandler namespace="Package"
24 * @version $Revision: 1.16 $
25 */
26 public class PackageTagsHandler extends AbstractProgramElementTagsHandler
27 {
28 /**
29 * Gets the name of a package, optionally applying any substitutions.
30 *
31 * @param pak package
32 * @param withSubstitution whether to apply any substitutions
33 * @return package name
34 */
35 public static String getPackageNameFor(XPackage pak, boolean withSubstitution)
36 {
37 return getPackageNameFor(pak.getName(), withSubstitution);
38 }
39
40 /**
41 * Gets a package name with any subsitutions applied.
42 *
43 * @param packageName package name
44 * @return package name
45 */
46 public static String getPackageNameFor(String packageName)
47 {
48 return getPackageNameFor(packageName, true);
49 }
50
51 /**
52 * Apply package substitutions. If <tt>useFirst</tt> is <tt>true</tt> , the first occurrence of <tt>substituteWith
53 * </tt> will be replaced by <tt>packages</tt> , else the one of the direct container of the current class.
54 *
55 * @param packageName The (current) package name, on which substitution shall take place.
56 * @param withSubstitution true if package substitutions shall take place.
57 * @return The package name after substitutions.
58 */
59 public static String getPackageNameFor(String packageName, boolean withSubstitution)
60 {
61 ArrayList packageSubstitutions = getPackageSubstitutions(DocletContext.getInstance().getActiveSubTask().getSubTaskName());
62
63 if (packageSubstitutions == null || !withSubstitution) {
64 return packageName;
65 }
66
67 for (int i = 0; i < packageSubstitutions.size(); i++) {
68 PackageSubstitution ps = (PackageSubstitution) packageSubstitutions.get(i);
69 StringTokenizer st = new StringTokenizer(ps.getPackages(), ",", false);
70
71 if (ps.getUseFirst() == false) {
72 while (st.hasMoreTokens()) {
73 String packages = st.nextToken();
74 String suffix = "." + packages;
75
76 if (packageName.endsWith(suffix)) {
77 if (ps.getSubstituteWith() == null || ps.getSubstituteWith().length() == 0) {
78 packageName = packageName.substring(0, packageName.length() - suffix.length());
79 }
80 else {
81 packageName = packageName.substring(0, packageName.length() -
82 suffix.length()) + '.' + ps.getSubstituteWith();
83 }
84 break;
85 }
86 }
87 }
88 else {
89 packageName = replaceInline(packageName, ps.getPackages(), ps.getSubstituteWith());
90 }
91 }
92
93 return packageName;
94 }
95
96 /**
97 * Gets any PackageSubstitutions defined for a specified subtask.
98 *
99 * @param subtaskName subtask name
100 * @return ArrayList of substitutions
101 */
102 public static ArrayList getPackageSubstitutions(String subtaskName)
103 {
104 // SubTask's packageSubstitutions has precedence over
105 // the global packageSubstitutions defined in DocletTask
106 ArrayList packageSubstitutions = null;
107 boolean supportsPackageSubstitutionInheritance = true;
108
109 Boolean supports = ((Boolean) DocletContext.getInstance().getConfigParam(subtaskName + ".packageSubstitutionInheritanceSupported"));
110
111 if (supports != null) {
112 supportsPackageSubstitutionInheritance = supports.booleanValue();
113 }
114
115 packageSubstitutions = (ArrayList) DocletContext.getInstance().getConfigParam(subtaskName + ".packageSubstitutions");
116
117 // nothing specified for subtask, inherit the one from DocletTask
118 if (supportsPackageSubstitutionInheritance && (packageSubstitutions == null || packageSubstitutions.isEmpty())) {
119 packageSubstitutions = (ArrayList) DocletContext.getInstance().getConfigParam("packageSubstitutions");
120 }
121
122 return packageSubstitutions;
123 }
124
125 /**
126 * Returns a package name as a path, after applying any substitutions.
127 *
128 * @param pak package
129 * @return package name as path
130 * @doc.tag type="content"
131 */
132 public static String packageNameAsPathFor(XPackage pak)
133 {
134 return getPackageNameFor(pak, true).replace('.', '/');
135 }
136
137 /**
138 * Returns a package name as a path, without applying any substitutions.
139 *
140 * @param pak package
141 * @return package name as path
142 * @doc.tag type="content"
143 */
144 public static String packageNameAsPathWithoutSubstitutionFor(XPackage pak)
145 {
146 return getPackageNameFor(pak, false).replace('.', '/');
147 }
148
149 /**
150 * Returns a package name as a path, after applying any substitutions.
151 *
152 * @param qualifiedName package name
153 * @return package name as path
154 * @doc.tag type="content"
155 */
156 public static String packageNameAsPathFor(String qualifiedName)
157 {
158 String qName = qualifiedName;
159
160 ArrayList pss = getPackageSubstitutions(DocletContext.getInstance().getActiveSubTask().getSubTaskName());
161
162 PackageSubstitution ps;
163
164 for (int i = 0; i < pss.size(); i++) {
165 ps = (PackageSubstitution) pss.get(i);
166 if (ps.getUseFirst() == true) {
167 qName = replaceInline(qName, ps.getPackages(), ps.getSubstituteWith());
168 }
169 }
170
171 return qName.replace('.', '/');
172 }
173
174 /**
175 * Replace the first occurrence of <code>oldOne</code> in <code>original</code> with <code>newOne</code>, or returns
176 * the original string if <code>oldOne</code> is not found.
177 *
178 * @param original String in which replacement should occour
179 * @param oldOne String to be replaced
180 * @param newOne String that replaces
181 * @return String original string with replacements
182 */
183 public static String replaceInline(String original, String oldOne, String newOne)
184 {
185 int index = original.indexOf(oldOne);
186
187 if (index > -1)
188 return original.substring(0, index) + newOne + original.substring(index + oldOne.length());
189 else
190 return original;
191 }
192
193 /**
194 * Returns the current package name. If we're in the context of a package iteration, this is the name of the current
195 * package. If we're in the context of a class iteration without a package iteration, return the name of the current
196 * class' package.
197 *
198 * @return current package name
199 * @exception XDocletException Description of Exception
200 * @doc.tag type="content"
201 */
202 public String packageName() throws XDocletException
203 {
204 if (getCurrentPackage() != null) {
205 // first try to get the name from current package. It exists if
206 return getCurrentPackage().getName();
207 }
208 else {
209 return getCurrentClass().getContainingPackage().getName();
210 }
211 }
212
213 /**
214 * Returns the not-full-qualified package name of the full-qualified class name specified in the body of this tag.
215 *
216 * @param template The body of the block tag
217 * @exception XDocletException Description of Exception
218 * @doc.tag type="block"
219 */
220 public void packageOf(String template) throws XDocletException
221 {
222 getEngine().print(getPackageNameFrom(template));
223 }
224
225 /**
226 * Writes the package declaration for the package name of the full-qualified class name specified in the body of
227 * this tag. No package declaration is written if the full-qualified class name has no package.
228 *
229 * @param template The body of the block tag
230 * @exception XDocletException Description of Exception
231 * @doc.tag type="block"
232 */
233 public void packageDeclarationOf(String template) throws XDocletException
234 {
235 String packageName = getPackageNameFrom(template);
236
237 if (packageName != null && packageName.length() > 0) {
238 getEngine().print("package " + packageName + ";");
239 }
240 }
241
242 /**
243 * Iterates over all packages loaded by XJavadoc. Subsequent calls to forAllClasses will only iterate over the
244 * classes in the current package.
245 *
246 * @param template The body of the block tag
247 * @param attributes The attributes of the template tag
248 * @exception XDocletException Description of Exception
249 * @doc.tag type="block"
250 * @doc.param name="abstract" optional="true" values="true,false" description="If true then accept
251 * abstract classes also; otherwise don't."
252 * @doc.param name="type" optional="true" description="For all classes by the type."
253 * @doc.param name="extent" optional="true" values="concrete-type,superclass,hierarchy"
254 * description="Specifies the extent of the type search. If concrete-type then only check the concrete type, if
255 * superclass then check also superclass, if hierarchy then search the whole hierarchy and find if the class is
256 * of the specified type. Default is hierarchy."
257 */
258 public void forAllPackages(String template, Properties attributes) throws XDocletException
259 {
260 Collection classes = getXJavaDoc().getSourceClasses();
261 SortedSet packages = new TreeSet();
262
263 for (Iterator i = classes.iterator(); i.hasNext(); ) {
264 XClass clazz = (XClass) i.next();
265
266 packages.add(clazz.getContainingPackage());
267 }
268
269 XPackage currentPackage = null;
270
271 for (Iterator packageIterator = packages.iterator(); packageIterator.hasNext(); ) {
272 currentPackage = (XPackage) packageIterator.next();
273 setCurrentPackage(currentPackage);
274 generate(template);
275 }
276 // restore current package to null, so subsequent class iterations can
277 // perform outside the context of a current package
278 setCurrentPackage(null);
279 }
280
281 /**
282 * Returns the current package name as a path.
283 *
284 * @return current package name as path
285 * @exception XDocletException Description of Exception
286 * @doc.tag type="content"
287 */
288 public String packageNameAsPath() throws XDocletException
289 {
290 return packageNameAsPathFor(packageName());
291 }
292
293 /**
294 * Returns the package name for the full-qualified class name specified in the body of the passed tag.
295 *
296 * @param template The body of the block tag
297 * @return the package name or an empty string if the full-qualified class name has no package
298 * @exception XDocletException Description of Exception
299 */
300 private String getPackageNameFrom(String template) throws XDocletException
301 {
302 try {
303 String fullClassName = getEngine().outputOf(template);
304 int pos = fullClassName.lastIndexOf('.');
305
306 if (pos < 0) {
307 return "";
308 }
309 else {
310 return getPackageNameFor(fullClassName.substring(0, pos), true);
311 }
312 }
313 catch (TemplateException ex) {
314 throw new XDocletException(ex, Translator.getString(XDocletMessages.class, XDocletMessages.METHOD_FAILED, new String[]{"packageOf"}));
315 }
316
317 }
318
319 /**
320 * It's good practice to put interfaces (such as remote/local interfaces, data objects and home interfaces) in a
321 * separate "interfaces" package rather than in the EJB bean implementation package. Previous versions of XDoclet
322 * dictated this behavior, so if package name of a bean ended with <code>.beans</code> or <code>.ejb</code>
323 * interfaces were put into .interfaces package. It's no more the case. You have full control over it. If you don't
324 * use a <code>packageSubstitution</code> element, then all interfaces are generated to the same package as the bean
325 * implementation class. But if you want to follow the pattern and put interfaces into a separate package you can,
326 * by providing the list of package name tails that interfaces of beans inside that packages should be placed into
327 * the package you define. For example interfaces of <code>test.ejb.CustomerBean</code> will be placed in <code>test.interfaces</code>
328 * by the following <code>packageSubstitution</code>:<p/>
329 *
330 * <pre><code>
331 *<packageSubstitution packages="ejb,beans" substituteWith="interfaces" />
332 *</code></pre> <p/>
333 *
334 * By using the <code>useFirst</code> attribute, you can tell XDoclet to substitute the first occurrence and not the
335 * last. <br/>
336 * Now if you have a structure like<p/>
337 *
338 * com.acme.foo.bar.ejb <br/>
339 * com.acme.baz.lala.ejb<p/>
340 *
341 * you want to gather all interfaces under one root/subtree like e.g. <p/>
342 *
343 * com.acme.interfaces.bar.* <br/>
344 * com.acme.interfaces.lala.*<p/>
345 *
346 * now you can say:<br/>
347 * <packagesubstitution packages="foo,baz" substituteWith="interfaces" useFirst="true"/>
348 *
349 * @created 10. september 2002
350 */
351 public static class PackageSubstitution implements Serializable
352 {
353 private String packages = null;
354 private String substituteWith = null;
355 private boolean useFirst = false;
356
357 /**
358 * Get the comma-separated list of packages to be substituted.
359 *
360 * @return package list
361 */
362 public String getPackages()
363 {
364 return packages;
365 }
366
367 /**
368 * Get the substitute package name.
369 *
370 * @return package
371 */
372 public String getSubstituteWith()
373 {
374 return substituteWith;
375 }
376
377 /**
378 * Return the useFirst attribute. This attribute specifies if the substitution can only appear at the end of a
379 * package (useFirst=false) or also in the middle.
380 *
381 * @return boolean
382 */
383 public boolean getUseFirst()
384 {
385 return this.useFirst;
386 }
387
388 /**
389 * Set the comma-separated list of packages to be substituted.
390 *
391 * @param packages The new Packages value
392 */
393 public void setPackages(String packages)
394 {
395 this.packages = packages;
396 }
397
398 /**
399 * Set the substitute package name.
400 *
401 * @param substituteWith The new SubstituteWith value
402 */
403 public void setSubstituteWith(String substituteWith)
404 {
405 this.substituteWith = substituteWith;
406 }
407
408 /**
409 * Specify whether the first occurrence of a package from the list should be substituted, or the last.
410 *
411 * @param first should the first occurrence be used or not?
412 */
413 public void setUseFirst(boolean first)
414 {
415 this.useFirst = first;
416
417 }
418 }
419 }