blob: d2e7302a1e686e2ad07cc83798612c451d0a3004 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2001 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26import com.sun.javadoc.ClassDoc;
27import com.sun.javadoc.MethodDoc;
28import com.sun.javadoc.RootDoc;
29import com.sun.javadoc.Tag;
30
31import java.beans.Introspector;
32
33import java.util.Enumeration;
34import java.util.Hashtable;
35import java.util.HashMap;
36import java.util.StringTokenizer;
37
38/**
39 * Properties supported and tag syntax:
40 *
41 * @beaninfo
42 * bound: flag
43 * constrained: flag
44 * expert: flag
45 * hidden: flag
46 * preferred: flag
47 * description: string
48 * displayname: string
49 * propertyeditorclass: string (with dots: foo.bar.MyPropertyEditor
50 * customizerclass: string (w/dots: foo.bar.MyCustomizer)
51 * attribute: key1 value1
52 * attribute: key2 value2
53 *
54 * TODO: getValue and genDocletInfo needs some cleaning.
55 *
56 * @author Hans Muller
57 * @author Rich Schiavi
58 * @author Mark Davidson
59 */
60public class GenDocletBeanInfo {
61
62 static String[] ATTRIBUTE_NAMES = { "bound",
63 "constrained",
64 "expert",
65 "hidden",
66 "preferred",
67 "displayname",
68 "propertyeditorclass",
69 "customizerclass",
70 "displayname",
71 "description",
72 "enum",
73 "attribute" };
74 private static boolean DEBUG = false;
75
76 private static String fileDir = "";
77 private static String templateDir = "";
78
79 public static final String TRUE = "true";
80 public static final String FALSE = "false";
81
82 /**
83 * Method called from the javadoc environment to determint the options length.
84 * Doclet options:
85 * -t template location
86 * -d outputdir
87 * -x true Enable debug output.
88 */
89 public static int optionLength(String option) {
90 // remind: this needs to be cleaned up
91 if (option.equals("-t"))
92 return 2;
93 if (option.equals("-d"))
94 return 2;
95 if (option.equals("-x"))
96 return 2;
97 return 0;
98 }
99
100 /** @beaninfo
101 * bound:true
102 * constrained:false
103 * expert:true
104 * hidden:true
105 * preferred:false
106 * description: the description of this method can
107 * do all sorts of funky things. if it \n
108 * is indented like this, we have to remove
109 * all char spaces greater than 2 and also any hard-coded \n
110 * newline characters and all newlines
111 * displayname: theString
112 * propertyeditorclass: foo.bar.MyPropertyEditorClass
113 * customizerclass: foo.bar.MyCustomizerClass
114 * attribute:key1 value1
115 * attribute: key2 value2
116 *
117 */
118 public static boolean start(RootDoc doc) {
119 readOptions(doc.options());
120
121 if (templateDir.length() == 0) {
122 System.err.println("-t option not specified");
123 return false;
124 }
125 if (fileDir.length() == 0) {
126 System.err.println("-d option not specified");
127 return false;
128 }
129
130 GenSwingBeanInfo generator = new GenSwingBeanInfo(fileDir, templateDir, DEBUG);
131 Hashtable dochash = new Hashtable();
132 DocBeanInfo dbi;
133
134 /* "javadoc Foo.java Bar.java" will return:
135 * "Foo Foo.I1 Foo.I2 Bar Bar.I1 Bar.I2"
136 * i.e., with all the innerclasses of classes specified in the command
137 * line. We don't want to generate BeanInfo for any of these inner
138 * classes, so we ignore these by remembering what the last outer
139 * class was. A hack, I admit, but makes the build faster.
140 */
141 String previousClass = null;
142
143 ClassDoc[] classes = doc.classes();
144
145 for (int cnt = 0; cnt < classes.length; cnt++) {
146 String className = classes[cnt].qualifiedName();
147 if (previousClass != null &&
148 className.startsWith(previousClass) &&
149 className.charAt(previousClass.length()) == '.') {
150 continue;
151 }
152 previousClass = className;
153
154 // XXX - debug
155 System.out.println("\n>>> Generating beaninfo for " + className + "...");
156
157 // Examine the javadoc tags and look for the the @beaninfo tag
158 // This first block looks at the javadoc for the class
159 Tag[] tags = classes[cnt].tags();
160 for (int i = 0; i < tags.length; i++) {
161 if (tags[i].kind().equalsIgnoreCase("@beaninfo")) {
162 if (DEBUG)
163 System.out.println("GenDocletBeanInfo: found @beaninfo tagged Class: " + tags[i].text());
164 dbi = genDocletInfo(tags[i].text(), classes[cnt].name());
165 dochash.put(dbi.name, dbi);
166 break;
167 }
168 }
169
170 // This block looks at the javadoc for the class methods.
171 int startPos = -1;
172 MethodDoc[] methods = classes[cnt].methods();
173 for (int j = 0; j < methods.length; j++) {
174 // actually don't "introspect" - look for all
175 // methods with a @beaninfo tag
176 tags = methods[j].tags();
177 for (int x = 0; x < tags.length; x++){
178 if (tags[x].kind().equalsIgnoreCase("@beaninfo")){
179 if ((methods[j].name().startsWith("get")) ||
180 (methods[j].name().startsWith("set")))
181 startPos = 3;
182 else if (methods[j].name().startsWith("is"))
183 startPos = 2;
184 else
185 startPos = 0;
186 String propDesc =
187 Introspector.decapitalize((methods[j].name()).substring(startPos));
188 if (DEBUG)
189 System.out.println("GenDocletBeanInfo: found @beaninfo tagged Method: " + tags[x].text());
190 dbi = genDocletInfo(tags[x].text(), propDesc);
191 dochash.put(dbi.name, dbi);
192 break;
193 }
194 }
195 }
196 if (DEBUG) {
197 // dump our classes doc beaninfo
198 System.out.println(">>>>DocletBeanInfo for class: " + classes[cnt].name());
199 Enumeration e = dochash.elements();
200 while (e.hasMoreElements()) {
201 DocBeanInfo db = (DocBeanInfo)e.nextElement();
202 System.out.println(db.toString());
203 }
204 }
205
206 // Use the generator to create the beaninfo code for the class.
207 generator.genBeanInfo(classes[cnt].containingPackage().name(),
208 classes[cnt].name(), dochash);
209 // reset the values!
210 dochash.clear();
211 } // end for loop
212 return true;
213 }
214
215 /**
216 * Reads the command line options.
217 * Side Effect, sets class variables templateDir, fileDir and DEBUG
218 */
219 private static void readOptions(String[][] options) {
220 // Parse the command line args
221 for (int i = 0; i < options.length; i++){
222 if (options[i][0].equals("-t")) {
223 templateDir = options[i][1];
224 } else if (options[i][0].equals("-d")) {
225 fileDir = options[i][1];
226 } else if (options[i][0].equals("-x")){
227 if (options[i][1].equals("true"))
228 DEBUG=true;
229 else
230 DEBUG=false;
231 }
232 }
233 }
234
235 /**
236 * Create a "BeanInfo" data structure from the tag. This is a data structure
237 * which contains all beaninfo data for a method or a class.
238 *
239 * @param text All the text after the @beaninfo tag.
240 * @param name Name of the property i.e., mnemonic for setMnemonic
241 */
242 private static DocBeanInfo genDocletInfo(String text, String name) {
243 int beanflags = 0;
244 String desc = "null";
245 String displayname = "null";
246 String propertyeditorclass = "null";
247 String customizerclass = "null";
248 String value = "null";
249 HashMap attribs = null;
250 HashMap enums = null;
251
252 int index;
253
254 for (int j = 0; j < ATTRIBUTE_NAMES.length; j++){
255 index = 0;
256 if ((index = text.indexOf(ATTRIBUTE_NAMES[j])) != -1){
257 value = getValue((text).substring(index),ATTRIBUTE_NAMES[j]);
258
259 if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("attribute")) {
260 attribs = getAttributeMap(value, " ");
261 }
262 if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("enum")) {
263 enums = getAttributeMap(value, " \n");
264 }
265 else if (ATTRIBUTE_NAMES[j].equals("displayname")){
266 displayname = value;
267 }
268 else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("propertyeditorclass")) {
269 propertyeditorclass = value;
270 }
271 else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("customizerclass")){
272 customizerclass = value;
273 }
274 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("bound"))
275 && (value.equalsIgnoreCase(TRUE)))
276 beanflags = beanflags | DocBeanInfo.BOUND;
277 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("expert"))
278 && (value.equalsIgnoreCase(TRUE)))
279 beanflags = beanflags | DocBeanInfo.EXPERT;
280 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("constrained"))
281 && (value.equalsIgnoreCase(TRUE)))
282 beanflags = beanflags | DocBeanInfo.CONSTRAINED;
283 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("hidden"))
284 && (value.equalsIgnoreCase(TRUE)))
285 beanflags = beanflags | DocBeanInfo.HIDDEN;
286 else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("preferred"))
287 && (value.equalsIgnoreCase(TRUE)))
288 beanflags = beanflags | DocBeanInfo.PREFERRED;
289 else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("description")){
290 desc = value;
291 }
292 }
293 }
294 /** here we create our doclet-beaninfo data structure, which we read in
295 * later if it has anything worthwhile
296 */
297
298 // Construct a new Descriptor class
299 return new DocBeanInfo(name, beanflags, desc,displayname,
300 propertyeditorclass, customizerclass,
301 attribs, enums);
302 }
303
304 /**
305 * Parses the substring and returns the cleaned up value for the attribute.
306 * @param substring Full String of the attrib tag.
307 * i.e., "attribute: visualUpdate true" will return "visualUpdate true";
308 */
309 private static String getValue(String substring, String prop) {
310 StringTokenizer t;
311 String value = "null";
312
313 try {
314 /** if the ATTRIBUTE_NAMES is NOT the description, then we
315 * parse until newline
316 * if it is the description we read until the next token
317 * and then look for a match in the last MAXMATCH index
318 * and truncate the description
319 * if it is the attribute we wead until no more
320 */
321 if (prop.equalsIgnoreCase("attribute")){
322 StringBuffer tmp = new StringBuffer();
323 try {
324 t = new StringTokenizer(substring, " :\n");
325 t.nextToken().trim();//the prop
326 // we want to return : key1 value1 key2 value2
327 while (t.hasMoreTokens()){
328 tmp.append(t.nextToken().trim()).append(" ");
329 tmp.append(t.nextToken().trim()).append(" ");
330 String test = t.nextToken().trim();
331 if (!(test.equalsIgnoreCase("attribute")))
332 break;
333 }
334 } catch (Exception e){
335 }
336 value = tmp.toString();
337 }
338 else if (prop.equalsIgnoreCase("enum")){
339 t = new StringTokenizer(substring, ":");
340 t.nextToken().trim(); // the prop we already know
341 StringBuffer tmp = new StringBuffer(t.nextToken().trim());
342 for (int i = 0; i < ATTRIBUTE_NAMES.length; i++){
343 if (tmp.toString().endsWith(ATTRIBUTE_NAMES[i])){
344 int len = ATTRIBUTE_NAMES[i].length();
345 // trim off that
346 tmp.setLength(tmp.length() - len);
347 break;
348 }
349 }
350 value = tmp.toString();
351 }
352 else if (prop.equalsIgnoreCase("description")){
353 t = new StringTokenizer(substring, ":");
354 t.nextToken().trim(); // the prop we already know
355 StringBuffer tmp = new StringBuffer(t.nextToken().trim());
356 for (int i = 0; i < ATTRIBUTE_NAMES.length; i++){
357 if (tmp.toString().endsWith(ATTRIBUTE_NAMES[i])){
358 int len = ATTRIBUTE_NAMES[i].length();
359 // trim off that
360 tmp.setLength(tmp.length() - len);
361 break;
362 }
363 }
364 value = hansalizeIt(tmp.toString());
365 }
366 else {
367 // Single value properties like bound: true
368 t = new StringTokenizer(substring, ":\n");
369 t.nextToken().trim(); // the prop we already know
370 value = t.nextToken().trim();
371 }
372
373 // now we need to look for a match of any of the
374 // property
375
376 return value;
377 }
378 catch (Exception e){
379 return "invalidValue";
380 }
381 }
382
383 /**
384 * Creates a HashMap containing the key value pair for the parsed values
385 * of the "attributes" and "enum" tags.
386 * ie. For attribute value: visualUpdate true
387 * The HashMap will have key: visualUpdate, value: true
388 */
389 private static HashMap getAttributeMap(String str, String delim) {
390 StringTokenizer t = new StringTokenizer(str, delim);
391 HashMap map = null;
392 String key;
393 String value;
394
395 int num = t.countTokens()/2;
396 if (num > 0) {
397 map = new HashMap();
398 for (int i = 0; i < num; i++) {
399 key = t.nextToken().trim();
400 value = t.nextToken().trim();
401 map.put(key, value);
402 }
403 }
404 return map;
405 }
406
407 // looks for extra spaces, \n hard-coded and invisible,etc
408 private static String hansalizeIt(String from){
409 char [] chars = from.toCharArray();
410 int len = chars.length;
411 int toss = 0;
412
413 // remove double spaces
414 for (int i = 0; i < len; i++){
415 if ((chars[i] == ' ')) {
416 if (i+1 < len) {
417 if ((chars[i+1] == ' ' ) || (chars[i+1] == '\n'))
418 {
419 --len;
420 System.arraycopy(chars,i+1,chars,i,len-i);
421 --i;
422 }
423 }
424 }
425
426 if (chars[i] == '\n'){
427 chars[i] = ' ';
428 i -= 2;
429 }
430
431 if (chars[i] == '\\') {
432 if (i+1 < len) {
433 if (chars[i+1] == 'n'){
434 chars[i+1] = ' ';
435 --len;
436 System.arraycopy(chars,i+1, chars,i, len-i);
437 --i;
438 }
439 }
440 }
441 }
442 return new String(chars,0,len);
443 }
444
445}