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