blob: cd6852ba1c5586487a4e7166d2079d0a26f2a18f [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2004 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 java.beans.BeanInfo;
27import java.beans.BeanDescriptor;
28import java.beans.Introspector;
29import java.beans.IntrospectionException;
30import java.beans.PropertyDescriptor;
31
32import java.io.*;
33
34import java.util.Hashtable;
35import java.util.HashMap;
36import java.util.Iterator;
37
38/**
39 * A utlity for generating a BeanInfo source file from a template and a
40 * Hashtable with hints that were generated from a doclet.
41 * it's neccessary to write things like the per property descriptions
42 * by hand. To run the application:
43 * <pre>
44 * java GenSwingBeanInfo <class name>
45 * </pre>
46 * Code for a bean info class is written to out. If the class is
47 * swing package, you don't need to fully specify its name.
48 *
49 * @author Hans Muller
50 * @author Rich Schiavi
51 * @author Mark Davidson
52 */
53public class GenSwingBeanInfo {
54 private final static String BEANINFO_SUFFIX = "BeanInfo.java";
55
56 // Tokens in @(...)
57 private final static String TOK_BEANPACKAGE = "BeanPackageName";
58 private final static String TOK_BEANCLASS = "BeanClassName";
59 private final static String TOK_BEANOBJECT = "BeanClassObject";
60 private final static String TOK_CLASSDESC = "ClassDescriptors";
61 private final static String TOK_BEANDESC = "BeanDescription";
62 private final static String TOK_PROPDESC = "BeanPropertyDescriptors";
63 private final static String TOK_ENUMVARS = "EnumVariables";
64
65 private String enumcode; // Generated code for enumerated properties.
66
67 private boolean DEBUG = false;
68
69 private String fileDir;
70 private String templateFilename;
71
72 /**
73 * Public constructor
74 * @param fileDir Location to put the generated source files.
75 * @param templateFilename Location of the BeanInfo template
76 * @param debug Flag to turn on debugging
77 */
78 public GenSwingBeanInfo(String fileDir, String templateFilename, boolean debug) {
79 this.fileDir = fileDir;
80 this.templateFilename = templateFilename;
81 this.DEBUG = debug;
82 }
83
84 /**
85 * Opens a BeanInfo PrintStream for the class.
86 */
87 private PrintStream initOutputFile(String classname) {
88 try {
89 OutputStream out = new FileOutputStream(fileDir + File.separator + classname + BEANINFO_SUFFIX);
90 BufferedOutputStream bout = new BufferedOutputStream(out);
91 return new PrintStream(out);
92 } catch (IOException e){
93 // System.err.println("GenSwingBeanInfo: " + e.toString());
94 }
95 return null;
96 }
97
98 private static void messageAndExit(String msg) {
99 System.err.println("\n" + msg);
100 System.exit(1);
101 }
102
103
104 /**
105 * Load the contents of the BeanInfo template into a string and
106 * return the string.
107 */
108 private String loadTemplate() {
109 String template = "<no template>";
110
111 try {
112 File file = new File(templateFilename);
113 DataInputStream stream = new DataInputStream(new FileInputStream(file));
114 BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
115 StringBuffer buffer = new StringBuffer();
116
117 int c;
118 while((c = reader.read()) != -1) {
119 buffer.append((char)c);
120 }
121
122 template = buffer.toString();
123 reader.close();
124 } catch (IOException e) {
125 System.out.println(e.getMessage());
126 messageAndExit("GenSwingBeanInfo: Couldn't load template: " + templateFilename + e);
127 }
128 return template;
129 }
130
131
132 /**
133 * Generates a string for the BeanDescriptor
134 */
135 private String genBeanDescriptor(DocBeanInfo dbi) {
136 String code = "";
137 int beanflags = dbi.beanflags;
138
139 // we support export, hidden, preferred
140 if ((beanflags & DocBeanInfo.EXPERT) != 0)
141 code += " sun.swing.BeanInfoUtils.EXPERT, Boolean.TRUE,\n";
142 if ((beanflags & DocBeanInfo.HIDDEN) !=0)
143 code += " sun.swing.BeanInfoUtils.HIDDEN, Boolean.TRUE,\n";
144 /* 1.2 only - make sure build flag build using 1.2 */
145 if ((beanflags & DocBeanInfo.PREFERRED) !=0)
146 code += " sun.swing.BeanInfoUtils.PREFERRED, Boolean.TRUE,\n";
147 if (!(dbi.customizerclass.equals("null")))
148 code += " sun.swing.BeanInfoUtils.CUSTOMIZERCLASS, " + dbi.customizerclass + ".class,\n";
149
150 if (dbi.attribs != null) {
151 code += genAttributes(dbi.attribs);
152 }
153
154 return code;
155 }
156
157 /**
158 * Generates the code for the attributes table.
159 */
160 private String genAttributes(HashMap attribs) {
161 StringBuffer code = new StringBuffer();
162 String key;
163 String value;
164
165 Iterator iterator = attribs.keySet().iterator();
166 while(iterator.hasNext()) {
167 key = (String)iterator.next();
168 value = (String)attribs.get(key);
169
170 if (value.equals("true") || value.equals("false")) {
171 // Substitute the "true" and "false" for codegen Boolean values.
172 if(value.equals("true"))
173 value = "Boolean.TRUE";
174 else
175 value = "Boolean.FALSE";
176
177 code.append(" \"").append(key).append("\", ").append(value).append(",\n");
178 } else {
179 code.append(" \"").append(key).append("\", \"").append(value).append("\",\n");
180 }
181 }
182 return code.toString();
183 }
184
185 /**
186 * Generates the code for the enumeration.
187 * XXX - side effect: Modifies the enumcode field variable.
188 */
189 private String genEnumeration(String propName, HashMap enums) {
190 String objectName = propName + "Enumeration";
191 String key;
192 String value;
193
194 StringBuffer code = new StringBuffer("\n\t\tObject[] ");
195 code.append(objectName).append(" = new Object[] { \n");
196
197 Iterator iterator = enums.keySet().iterator();
198 while(iterator.hasNext()) {
199 key = (String)iterator.next();
200 value = (String)enums.get(key);
201
202 code.append("\t\t\t\"").append(key).append("\" , new Integer(");
203 code.append(value).append("), \"").append(value).append("\",\n");
204 }
205 // Close the statically initialized Object[]
206 code.replace(code.length() - 2, code.length(), "\n\t\t};\n");
207
208 // Add this string to the enumeration code.
209 enumcode += code.toString();
210
211 // Return the PropertyDescriptor init string;
212 return " \"enumerationValues\", " + objectName + ",\n";
213 }
214
215 /**
216 * Generate the createPropertyDescriptor() calls, one per property.
217 * A fully specified createPropertyDescriptor() call looks like this:
218 * <pre>
219 * createPropertyDescriptor("contentPane", new Object[] {
220 * BOUND, Boolean.TRUE,
221 * CONSTRAINED, Boolean.TRUE,
222 * PROPERTYEDITORCLASS, package.MyEditor.cl
223 * WRITEMETHOD, "setContentPane",
224 * DISPLAYNAME, "contentPane",
225 * EXPERT, Boolean.FALSE,
226 * HIDDEN, Boolean.FALSE,
227 * PREFERRED, Boolean.TRUE,
228 * SHORTDESCRIPTION, "A top level window with a window manager border",
229 * "random attribute","random value"
230 * }
231 * );
232 * </pre>
233 *
234 * @param info The actual BeanInfo class generated from from the Intospector.
235 * @param dochash Set of DocBeanInfo pairs for each property. This information
236 * is used to suplement the instrospected properties.
237 * @return A snippet of source code which would construct all the PropertyDescriptors.
238 */
239 private String genPropertyDescriptors(BeanInfo info, Hashtable dochash) {
240 String code = "";
241 enumcode = " "; // code for enumerated properties.
242 PropertyDescriptor[] pds = info.getPropertyDescriptors();
243 boolean hash_match = false;
244 DocBeanInfo dbi = null;
245
246 for(int i = 0; i < pds.length; i++) {
247 if (pds[i].getReadMethod() != null) {
248 code += "\ncreatePropertyDescriptor(\"" + pds[i].getName() + "\", new Object[] {\n";
249
250 if (DEBUG)
251 System.out.println("Introspected propertyDescriptor: " + pds[i].getName());
252
253 if (dochash.size() > 0 && dochash.containsKey(pds[i].getName())) {
254 dbi = (DocBeanInfo)dochash.remove(pds[i].getName());
255 // override/set properties on this *introspected*
256 // BeanInfo pds using our DocBeanInfo class values
257 setDocInfoProps(dbi, pds[i]);
258 hash_match = true;
259 if (DEBUG)
260 System.out.println("DocBeanInfo class exists for propertyDescriptor: " + pds[i].getName() + "\n");
261 } else {
262 hash_match = false;
263 }
264
265 // Do I need to do anything with this property descriptor
266 if (hash_match) {
267 if ((dbi.beanflags & DocBeanInfo.BOUND) != 0) {
268 code += " sun.swing.BeanInfoUtils.BOUND, Boolean.TRUE,\n";
269 } else {
270 code += " sun.swing.BeanInfoUtils.BOUND, Boolean.FALSE,\n";
271 }
272 }
273
274 if (pds[i].isConstrained()) {
275 code += " sun.swing.BeanInfoUtils.CONSTRAINED, Boolean.TRUE,\n";
276 }
277
278 if (pds[i].getPropertyEditorClass() != null) {
279 String className = pds[i].getPropertyEditorClass().getName();
280 code += " sun.swing.BeanInfoUtils.PROPERTYEDITORCLASS, " + className + ".class,\n";
281 } else if ((hash_match) && (!(dbi.propertyeditorclass.equals("null")))) {
282 code += " sun.swing.BeanInfoUtils.PROPERTYEDITORCLASS, " + dbi.propertyeditorclass + ".class,\n";
283 }
284
285 if ((hash_match) && (!(dbi.customizerclass.equals("null")))) {
286 code += " sun.swing.BeanInfoUtils.CUSTOMIZERCLASS, " + dbi.customizerclass + ".class,\n";
287 }
288
289 if ((hash_match) && (dbi.enums != null)) {
290 code += genEnumeration(pds[i].getName(), dbi.enums);
291 }
292
293 if (!pds[i].getDisplayName().equals(pds[i].getName())) {
294 code += " sun.swing.BeanInfoUtils.DISPLAYNAME, \"" + pds[i].getDisplayName() + "\",\n";
295 }
296
297 if (pds[i].isExpert()) {
298 code += " sun.swing.BeanInfoUtils.EXPERT, Boolean.TRUE,\n";
299 }
300
301 if (pds[i].isHidden()) {
302 code += " sun.swing.BeanInfoUtils.HIDDEN, Boolean.TRUE,\n";
303 }
304
305 if (pds[i].isPreferred()) {
306 code += " sun.swing.BeanInfoUtils.PREFERRED, Boolean.TRUE,\n";
307 }
308
309 // user attributes
310 if (hash_match) {
311 if (dbi.attribs != null) {
312 code += genAttributes(dbi.attribs);
313 }
314 }
315 code += " sun.swing.BeanInfoUtils.SHORTDESCRIPTION, \"" + pds[i].getShortDescription() + "\",\n";
316
317 // Print the closing brackets. If this is the last array initializer,
318 // don't print the trailing comma.
319 if (i == (pds.length - 1)) {
320 code += " }\n)\n";
321 } else {
322 code += " }\n),\n";
323 }
324
325 } // end if ( readMethod != null )
326 } // end for
327 return code;
328 }
329
330 /**
331 * Sets properties from the BeanInfo supplement on the
332 * introspected PropertyDescriptor
333 */
334 private void setDocInfoProps(DocBeanInfo dbi, PropertyDescriptor pds) {
335 int beanflags = dbi.beanflags;
336
337 if ((beanflags & DocBeanInfo.BOUND) != 0)
338 pds.setBound(true);
339 if ((beanflags & DocBeanInfo.EXPERT) != 0)
340 pds.setExpert(true);
341 if ((beanflags & DocBeanInfo.CONSTRAINED) != 0)
342 pds.setConstrained(true);
343 if ((beanflags & DocBeanInfo.HIDDEN) !=0)
344 pds.setHidden(true);
345 if ((beanflags & DocBeanInfo.PREFERRED) !=0)
346 pds.setPreferred(true);
347
348 if (!(dbi.desc.equals("null"))){
349 pds.setShortDescription(dbi.desc);
350 }
351 if (!(dbi.displayname.equals("null"))){
352 pds.setDisplayName(dbi.displayname);
353 }
354 }
355
356 /**
357 * Generates the BeanInfo source file using instrospection and a
358 * Hashtable full of hints. This the only public method in this class.
359 *
360 * @param classname Root name of the class. i.e., JButton
361 * @param dochash A hashtable containing the DocBeanInfo.
362 */
363 public void genBeanInfo(String packageName, String classname, Hashtable dochash) {
364 // The following initial values are just examples. All of these
365 // fields are initialized below.
366 String beanClassName = "JInternalFrame";
367 String beanClassObject = "javax.swing.JInternalFrame.class";
368 String beanDescription = "<A description of this component>.";
369 String beanPropertyDescriptors = "<createSwingPropertyDescriptor code>";
370 String classPropertyDescriptors = "<createSwingClassPropertyDescriptor code>";
371
372 Class cls = getClass(packageName, classname);
373 if (cls == null){
374 messageAndExit("Can't find class: " + classname);
375 }
376
377 // Get the output stream.
378 PrintStream out = initOutputFile(classname);
379
380 // Run the Introspector and initialize the variables
381
382 BeanInfo beanInfo = null;
383 BeanDescriptor beanDescriptor = null;
384
385 try {
386 if (cls == javax.swing.JComponent.class) {
387 // Go all the way up the heirarchy for JComponent
388 beanInfo = Introspector.getBeanInfo(cls);
389 } else {
390 beanInfo = Introspector.getBeanInfo(cls, cls.getSuperclass());
391 }
392 beanDescriptor = beanInfo.getBeanDescriptor();
393 beanDescription = beanDescriptor.getShortDescription();
394 } catch (IntrospectionException e) {
395 messageAndExit("Introspection failed for " + cls.getName() + " " + e);
396 }
397
398 beanClassName = beanDescriptor.getName();
399 beanClassObject = cls.getName() + ".class";
400
401 if (DEBUG){
402 System.out.println(">>>>GenSwingBeanInfo class: " + beanClassName);
403 }
404 // Generate the Class BeanDescriptor information first
405 if (dochash.size() > 0) {
406 if (dochash.containsKey(beanClassName)) {
407 DocBeanInfo dbi = (DocBeanInfo)dochash.remove(beanClassName);
408 classPropertyDescriptors = genBeanDescriptor(dbi);
409 if (DEBUG)
410 System.out.println("ClassPropertyDescriptors: " + classPropertyDescriptors);
411 if (!(dbi.desc.equals("null")))
412 beanDescription = dbi.desc;
413 } else
414 beanDescription = beanDescriptor.getShortDescription();
415 } else
416 beanDescription = beanDescriptor.getShortDescription();
417
418 // Generate the Property descriptors
419 beanPropertyDescriptors = genPropertyDescriptors(beanInfo,dochash);
420
421 // Dump the template to out, substituting values for
422 // @(token) tokens as they're encountered.
423
424 int currentIndex = 0;
425 // not loading this to get around build issue for now
426 String template = loadTemplate();
427
428 // This loop substitutes the "@(...)" tags in the template with the ones for the
429 // current class.
430 while (currentIndex < template.length()) {
431 // Find the Token
432 int tokenStart = template.indexOf("@(", currentIndex);
433 if (tokenStart != -1) {
434 out.print(template.substring(currentIndex, tokenStart));
435
436 int tokenEnd = template.indexOf(")", tokenStart);
437 if (tokenEnd == -1) {
438 messageAndExit("Bad @(<token>) beginning at " + tokenStart);
439 }
440 String token = template.substring(tokenStart+2, tokenEnd);
441
442 if (token.equals(TOK_BEANCLASS)) {
443 out.print(beanClassName);
444 } else if (token.equals(TOK_CLASSDESC)) {
445 if (!(classPropertyDescriptors.equals("<createSwingClassPropertyDescriptor code>"))) {
446 printDescriptors(out, classPropertyDescriptors, template, tokenStart);
447 }
448 } else if (token.equals(TOK_BEANPACKAGE)){
449 out.print(packageName);
450 } else if (token.equals(TOK_BEANOBJECT)) {
451 out.print(beanClassObject);
452 } else if (token.equals(TOK_BEANDESC)) {
453 out.print(beanDescription);
454 } else if (token.equals(TOK_ENUMVARS)){
455 out.print(enumcode);
456 } else if (token.equals(TOK_PROPDESC)) {
457 printDescriptors(out, beanPropertyDescriptors, template, tokenStart);
458 } else if (token.equals("#")) {
459 // Ignore the @(#) Version Control tag if it exists.
460 } else {
461 messageAndExit("Unrecognized token @(" + token + ")");
462 }
463 currentIndex = tokenEnd + 1;
464 } else {
465 // tokenStart == -1 - We are finsihed.
466 out.print(template.substring(currentIndex, template.length()));
467 break;
468 }
469 }
470 out.close();
471 }
472
473 /**
474 * Returns the class from the package name and the class root name.
475 *
476 * @param packageName The name of the package of the containing class.
477 * @param rootname The root name of the class. i.e, JButton
478 * @return The class instance or null.
479 */
480 private Class getClass(String packageName, String rootname) {
481 Class cls = null;
482 String classname = rootname;
483
484 if (packageName != null || !packageName.equals("")) {
485 classname = packageName + "." + rootname;
486 }
487
488 try {
489 cls = Class.forName(classname);
490 } catch (ClassNotFoundException e) {
491 // Fail silently.
492 }
493 return cls;
494 }
495
496 /**
497 * Prints the formated descriptors to the PrintStream
498 * @param out Open PrintStream
499 * @param s String descriptor
500 * @param template Template
501 * @param tokenStart Index into the template
502 */
503 private void printDescriptors(PrintStream out, String s,
504 String template, int tokenStart) {
505 String indent = "";
506
507 // Find the newline that preceeds @(BeanPropertyDescriptors) to
508 // calculate the indent.
509 for (int i = tokenStart; i >= 0; i--) {
510 if (template.charAt(i) == '\n') {
511 char[] chars = new char[tokenStart - i];
512 for (int j = 0; j < chars.length; j++) {
513 chars[j] = ' ';
514 }
515 indent = new String(chars);
516 break;
517 }
518 }
519
520 int i = 0;
521 while(i < s.length()) {
522 int nlIndex = s.indexOf('\n', i);
523 out.print(s.substring(i, nlIndex+1));
524 out.print(indent);
525 i = nlIndex + 1;
526 }
527 }
528
529
530}