J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | import com.sun.javadoc.ClassDoc; |
| 27 | import com.sun.javadoc.MethodDoc; |
| 28 | import com.sun.javadoc.RootDoc; |
| 29 | import com.sun.javadoc.Tag; |
| 30 | |
| 31 | import java.beans.Introspector; |
| 32 | |
| 33 | import java.util.Enumeration; |
| 34 | import java.util.Hashtable; |
| 35 | import java.util.HashMap; |
| 36 | import 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 | */ |
| 60 | public 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 | } |