| /* |
| * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package build.tools.swingbeaninfo; |
| |
| import com.sun.javadoc.ClassDoc; |
| import com.sun.javadoc.MethodDoc; |
| import com.sun.javadoc.RootDoc; |
| import com.sun.javadoc.Tag; |
| |
| import java.beans.Introspector; |
| |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.HashMap; |
| import java.util.StringTokenizer; |
| |
| /** |
| * Properties supported and tag syntax: |
| * |
| * @beaninfo |
| * bound: flag |
| * constrained: flag |
| * expert: flag |
| * hidden: flag |
| * preferred: flag |
| * description: string |
| * displayname: string |
| * propertyeditorclass: string (with dots: foo.bar.MyPropertyEditor |
| * customizerclass: string (w/dots: foo.bar.MyCustomizer) |
| * attribute: key1 value1 |
| * attribute: key2 value2 |
| * |
| * TODO: getValue and genDocletInfo needs some cleaning. |
| * |
| * @author Hans Muller |
| * @author Rich Schiavi |
| * @author Mark Davidson |
| */ |
| public class GenDocletBeanInfo { |
| |
| static String[] ATTRIBUTE_NAMES = { "bound", |
| "constrained", |
| "expert", |
| "hidden", |
| "preferred", |
| "displayname", |
| "propertyeditorclass", |
| "customizerclass", |
| "displayname", |
| "description", |
| "enum", |
| "attribute" }; |
| private static boolean DEBUG = false; |
| |
| private static String fileDir = ""; |
| private static String templateDir = ""; |
| |
| public static final String TRUE = "true"; |
| public static final String FALSE = "false"; |
| |
| /** |
| * Method called from the javadoc environment to determint the options length. |
| * Doclet options: |
| * -t template location |
| * -d outputdir |
| * -x true Enable debug output. |
| */ |
| public static int optionLength(String option) { |
| // remind: this needs to be cleaned up |
| if (option.equals("-t")) |
| return 2; |
| if (option.equals("-d")) |
| return 2; |
| if (option.equals("-x")) |
| return 2; |
| return 0; |
| } |
| |
| /** @beaninfo |
| * bound:true |
| * constrained:false |
| * expert:true |
| * hidden:true |
| * preferred:false |
| * description: the description of this method can |
| * do all sorts of funky things. if it \n |
| * is indented like this, we have to remove |
| * all char spaces greater than 2 and also any hard-coded \n |
| * newline characters and all newlines |
| * displayname: theString |
| * propertyeditorclass: foo.bar.MyPropertyEditorClass |
| * customizerclass: foo.bar.MyCustomizerClass |
| * attribute:key1 value1 |
| * attribute: key2 value2 |
| * |
| */ |
| public static boolean start(RootDoc doc) { |
| readOptions(doc.options()); |
| |
| if (templateDir.length() == 0) { |
| System.err.println("-t option not specified"); |
| return false; |
| } |
| if (fileDir.length() == 0) { |
| System.err.println("-d option not specified"); |
| return false; |
| } |
| |
| GenSwingBeanInfo generator = new GenSwingBeanInfo(fileDir, templateDir, DEBUG); |
| Hashtable dochash = new Hashtable(); |
| DocBeanInfo dbi; |
| |
| /* "javadoc Foo.java Bar.java" will return: |
| * "Foo Foo.I1 Foo.I2 Bar Bar.I1 Bar.I2" |
| * i.e., with all the innerclasses of classes specified in the command |
| * line. We don't want to generate BeanInfo for any of these inner |
| * classes, so we ignore these by remembering what the last outer |
| * class was. A hack, I admit, but makes the build faster. |
| */ |
| String previousClass = null; |
| |
| ClassDoc[] classes = doc.classes(); |
| |
| for (int cnt = 0; cnt < classes.length; cnt++) { |
| String className = classes[cnt].qualifiedName(); |
| if (previousClass != null && |
| className.startsWith(previousClass) && |
| className.charAt(previousClass.length()) == '.') { |
| continue; |
| } |
| previousClass = className; |
| |
| // XXX - debug |
| System.out.println("\n>>> Generating beaninfo for " + className + "..."); |
| |
| // Examine the javadoc tags and look for the the @beaninfo tag |
| // This first block looks at the javadoc for the class |
| Tag[] tags = classes[cnt].tags(); |
| for (int i = 0; i < tags.length; i++) { |
| if (tags[i].kind().equalsIgnoreCase("@beaninfo")) { |
| if (DEBUG) |
| System.out.println("GenDocletBeanInfo: found @beaninfo tagged Class: " + tags[i].text()); |
| dbi = genDocletInfo(tags[i].text(), classes[cnt].name()); |
| dochash.put(dbi.name, dbi); |
| break; |
| } |
| } |
| |
| // This block looks at the javadoc for the class methods. |
| int startPos = -1; |
| MethodDoc[] methods = classes[cnt].methods(); |
| for (int j = 0; j < methods.length; j++) { |
| // actually don't "introspect" - look for all |
| // methods with a @beaninfo tag |
| tags = methods[j].tags(); |
| for (int x = 0; x < tags.length; x++){ |
| if (tags[x].kind().equalsIgnoreCase("@beaninfo")){ |
| if ((methods[j].name().startsWith("get")) || |
| (methods[j].name().startsWith("set"))) |
| startPos = 3; |
| else if (methods[j].name().startsWith("is")) |
| startPos = 2; |
| else |
| startPos = 0; |
| String propDesc = |
| Introspector.decapitalize((methods[j].name()).substring(startPos)); |
| if (DEBUG) |
| System.out.println("GenDocletBeanInfo: found @beaninfo tagged Method: " + tags[x].text()); |
| dbi = genDocletInfo(tags[x].text(), propDesc); |
| dochash.put(dbi.name, dbi); |
| break; |
| } |
| } |
| } |
| if (DEBUG) { |
| // dump our classes doc beaninfo |
| System.out.println(">>>>DocletBeanInfo for class: " + classes[cnt].name()); |
| Enumeration e = dochash.elements(); |
| while (e.hasMoreElements()) { |
| DocBeanInfo db = (DocBeanInfo)e.nextElement(); |
| System.out.println(db.toString()); |
| } |
| } |
| |
| // Use the generator to create the beaninfo code for the class. |
| generator.genBeanInfo(classes[cnt].containingPackage().name(), |
| classes[cnt].name(), dochash); |
| // reset the values! |
| dochash.clear(); |
| } // end for loop |
| return true; |
| } |
| |
| /** |
| * Reads the command line options. |
| * Side Effect, sets class variables templateDir, fileDir and DEBUG |
| */ |
| private static void readOptions(String[][] options) { |
| // Parse the command line args |
| for (int i = 0; i < options.length; i++){ |
| if (options[i][0].equals("-t")) { |
| templateDir = options[i][1]; |
| } else if (options[i][0].equals("-d")) { |
| fileDir = options[i][1]; |
| } else if (options[i][0].equals("-x")){ |
| if (options[i][1].equals("true")) |
| DEBUG=true; |
| else |
| DEBUG=false; |
| } |
| } |
| } |
| |
| /** |
| * Create a "BeanInfo" data structure from the tag. This is a data structure |
| * which contains all beaninfo data for a method or a class. |
| * |
| * @param text All the text after the @beaninfo tag. |
| * @param name Name of the property i.e., mnemonic for setMnemonic |
| */ |
| private static DocBeanInfo genDocletInfo(String text, String name) { |
| int beanflags = 0; |
| String desc = "null"; |
| String displayname = "null"; |
| String propertyeditorclass = "null"; |
| String customizerclass = "null"; |
| String value = "null"; |
| HashMap attribs = null; |
| HashMap enums = null; |
| |
| int index; |
| |
| for (int j = 0; j < ATTRIBUTE_NAMES.length; j++){ |
| index = 0; |
| if ((index = text.indexOf(ATTRIBUTE_NAMES[j])) != -1){ |
| value = getValue((text).substring(index),ATTRIBUTE_NAMES[j]); |
| |
| if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("attribute")) { |
| attribs = getAttributeMap(value, " "); |
| } |
| if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("enum")) { |
| enums = getAttributeMap(value, " \n"); |
| } |
| else if (ATTRIBUTE_NAMES[j].equals("displayname")){ |
| displayname = value; |
| } |
| else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("propertyeditorclass")) { |
| propertyeditorclass = value; |
| } |
| else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("customizerclass")){ |
| customizerclass = value; |
| } |
| else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("bound")) |
| && (value.equalsIgnoreCase(TRUE))) |
| beanflags = beanflags | DocBeanInfo.BOUND; |
| else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("expert")) |
| && (value.equalsIgnoreCase(TRUE))) |
| beanflags = beanflags | DocBeanInfo.EXPERT; |
| else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("constrained")) |
| && (value.equalsIgnoreCase(TRUE))) |
| beanflags = beanflags | DocBeanInfo.CONSTRAINED; |
| else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("hidden")) |
| && (value.equalsIgnoreCase(TRUE))) |
| beanflags = beanflags | DocBeanInfo.HIDDEN; |
| else if ((ATTRIBUTE_NAMES[j].equalsIgnoreCase("preferred")) |
| && (value.equalsIgnoreCase(TRUE))) |
| beanflags = beanflags | DocBeanInfo.PREFERRED; |
| else if (ATTRIBUTE_NAMES[j].equalsIgnoreCase("description")){ |
| desc = value; |
| } |
| } |
| } |
| /** here we create our doclet-beaninfo data structure, which we read in |
| * later if it has anything worthwhile |
| */ |
| |
| // Construct a new Descriptor class |
| return new DocBeanInfo(name, beanflags, desc,displayname, |
| propertyeditorclass, customizerclass, |
| attribs, enums); |
| } |
| |
| /** |
| * Parses the substring and returns the cleaned up value for the attribute. |
| * @param substring Full String of the attrib tag. |
| * i.e., "attribute: visualUpdate true" will return "visualUpdate true"; |
| */ |
| private static String getValue(String substring, String prop) { |
| StringTokenizer t; |
| String value = "null"; |
| |
| try { |
| /** if the ATTRIBUTE_NAMES is NOT the description, then we |
| * parse until newline |
| * if it is the description we read until the next token |
| * and then look for a match in the last MAXMATCH index |
| * and truncate the description |
| * if it is the attribute we wead until no more |
| */ |
| if (prop.equalsIgnoreCase("attribute")){ |
| StringBuffer tmp = new StringBuffer(); |
| try { |
| t = new StringTokenizer(substring, " :\n"); |
| t.nextToken().trim();//the prop |
| // we want to return : key1 value1 key2 value2 |
| while (t.hasMoreTokens()){ |
| tmp.append(t.nextToken().trim()).append(" "); |
| tmp.append(t.nextToken().trim()).append(" "); |
| String test = t.nextToken().trim(); |
| if (!(test.equalsIgnoreCase("attribute"))) |
| break; |
| } |
| } catch (Exception e){ |
| } |
| value = tmp.toString(); |
| } |
| else if (prop.equalsIgnoreCase("enum")){ |
| t = new StringTokenizer(substring, ":"); |
| t.nextToken().trim(); // the prop we already know |
| StringBuffer tmp = new StringBuffer(t.nextToken().trim()); |
| for (int i = 0; i < ATTRIBUTE_NAMES.length; i++){ |
| if (tmp.toString().endsWith(ATTRIBUTE_NAMES[i])){ |
| int len = ATTRIBUTE_NAMES[i].length(); |
| // trim off that |
| tmp.setLength(tmp.length() - len); |
| break; |
| } |
| } |
| value = tmp.toString(); |
| } |
| else if (prop.equalsIgnoreCase("description")){ |
| t = new StringTokenizer(substring, ":"); |
| t.nextToken().trim(); // the prop we already know |
| StringBuffer tmp = new StringBuffer(t.nextToken().trim()); |
| for (int i = 0; i < ATTRIBUTE_NAMES.length; i++){ |
| if (tmp.toString().endsWith(ATTRIBUTE_NAMES[i])){ |
| int len = ATTRIBUTE_NAMES[i].length(); |
| // trim off that |
| tmp.setLength(tmp.length() - len); |
| break; |
| } |
| } |
| value = hansalizeIt(tmp.toString()); |
| } |
| else { |
| // Single value properties like bound: true |
| t = new StringTokenizer(substring, ":\n"); |
| t.nextToken().trim(); // the prop we already know |
| value = t.nextToken().trim(); |
| } |
| |
| // now we need to look for a match of any of the |
| // property |
| |
| return value; |
| } |
| catch (Exception e){ |
| return "invalidValue"; |
| } |
| } |
| |
| /** |
| * Creates a HashMap containing the key value pair for the parsed values |
| * of the "attributes" and "enum" tags. |
| * ie. For attribute value: visualUpdate true |
| * The HashMap will have key: visualUpdate, value: true |
| */ |
| private static HashMap getAttributeMap(String str, String delim) { |
| StringTokenizer t = new StringTokenizer(str, delim); |
| HashMap map = null; |
| String key; |
| String value; |
| |
| int num = t.countTokens()/2; |
| if (num > 0) { |
| map = new HashMap(); |
| for (int i = 0; i < num; i++) { |
| key = t.nextToken().trim(); |
| value = t.nextToken().trim(); |
| map.put(key, value); |
| } |
| } |
| return map; |
| } |
| |
| // looks for extra spaces, \n hard-coded and invisible,etc |
| private static String hansalizeIt(String from){ |
| char [] chars = from.toCharArray(); |
| int len = chars.length; |
| int toss = 0; |
| |
| // remove double spaces |
| for (int i = 0; i < len; i++){ |
| if ((chars[i] == ' ')) { |
| if (i+1 < len) { |
| if ((chars[i+1] == ' ' ) || (chars[i+1] == '\n')) |
| { |
| --len; |
| System.arraycopy(chars,i+1,chars,i,len-i); |
| --i; |
| } |
| } |
| } |
| |
| if (chars[i] == '\n'){ |
| chars[i] = ' '; |
| i -= 2; |
| } |
| |
| if (chars[i] == '\\') { |
| if (i+1 < len) { |
| if (chars[i+1] == 'n'){ |
| chars[i+1] = ' '; |
| --len; |
| System.arraycopy(chars,i+1, chars,i, len-i); |
| --i; |
| } |
| } |
| } |
| } |
| return new String(chars,0,len); |
| } |
| |
| } |