0001: /*
0002: * Copyright 2005 Joe Walker
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.directwebremoting.drapgen.loader.gi;
0017:
0018: import java.io.File;
0019: import java.util.ArrayList;
0020: import java.util.HashMap;
0021: import java.util.HashSet;
0022: import java.util.LinkedList;
0023: import java.util.List;
0024: import java.util.Map;
0025: import java.util.Set;
0026: import java.util.SortedSet;
0027: import java.util.Stack;
0028: import java.util.TreeSet;
0029: import java.util.concurrent.ExecutorService;
0030: import java.util.concurrent.Executors;
0031: import java.util.concurrent.TimeUnit;
0032:
0033: import nu.xom.Attribute;
0034: import nu.xom.Builder;
0035: import nu.xom.Document;
0036: import nu.xom.Element;
0037: import nu.xom.Elements;
0038: import nu.xom.Node;
0039: import nu.xom.Nodes;
0040: import nu.xom.XPathException;
0041:
0042: import org.directwebremoting.drapgen.ast.Constructor;
0043: import org.directwebremoting.drapgen.ast.Field;
0044: import org.directwebremoting.drapgen.ast.Method;
0045: import org.directwebremoting.drapgen.ast.Parameter;
0046: import org.directwebremoting.drapgen.ast.Project;
0047: import org.directwebremoting.drapgen.ast.Type;
0048: import org.directwebremoting.drapgen.loader.Loader;
0049: import org.directwebremoting.drapgen.util.XomHelper;
0050: import org.directwebremoting.drapgen.util.XomHelper.ElementBlock;
0051: import org.directwebremoting.fsguide.FileSystemGuide;
0052: import org.directwebremoting.fsguide.Visitor;
0053: import org.directwebremoting.util.Logger;
0054:
0055: /**
0056: * @author Joe Walker [joe at getahead dot ltd dot uk]
0057: */
0058: public class GiLoader implements Loader {
0059: /**
0060: * @param source Where we read the API XML source from
0061: */
0062: public GiLoader(File source) throws Exception {
0063: log.info("Loading GI API from: " + source.getCanonicalPath());
0064: load(source);
0065: }
0066:
0067: /**
0068: * @param source Where we read the API XML source from
0069: */
0070: protected void load(File source) throws Exception {
0071: loadGiClasses(source);
0072:
0073: applyPreProcessingHacks();
0074: copyMixinFunctions();
0075: trimDuplicateMethods();
0076: applyPostProcessingHacks();
0077: }
0078:
0079: /**
0080: *
0081: */
0082: protected void applyPreProcessingHacks() {
0083: log
0084: .warn("jsx3.gui.Alerts.alert(): Type of parameter 'objParams' should be 'String'");
0085: changeAttributeValue(
0086: "jsx3.gui.Alerts",
0087: "/interface/method[@name='alert']/param[@name='objParams']/type",
0088: "name", "String");
0089:
0090: log
0091: .warn("jsx3.gui.Alerts.prompt(): Type of parameter 'objParams' should be 'String'");
0092: changeAttributeValue(
0093: "jsx3.gui.Alerts",
0094: "/interface/method[@name='prompt']/param[@name='objParams']/type",
0095: "name", "String");
0096:
0097: log
0098: .warn("jsx3.gui.Alerts.confirm(): Type of parameter 'objParams' should be 'String'");
0099: changeAttributeValue(
0100: "jsx3.gui.Alerts",
0101: "/interface/method[@name='confirm']/param[@name='objParams']/type",
0102: "name", "String");
0103:
0104: log
0105: .warn("jsx3.chart.PlotChart.DEFAULT_MAX_POINT_RADIUS: Type of number can't be 'String' assuming 'int'");
0106: changeAttributeValue("jsx3.chart.PlotChart",
0107: "/class/field[@name='DEFAULT_MAX_POINT_RADIUS']/type",
0108: "name", "int");
0109:
0110: log
0111: .warn("jsx3.gui.Matrix.MINIMUM_COLUMN_WIDTH: Type of number can't be 'String' assuming 'int'");
0112: changeAttributeValue("jsx3.gui.Matrix",
0113: "/class/field[@name='MINIMUM_COLUMN_WIDTH']/type",
0114: "name", "int");
0115: }
0116:
0117: /**
0118: *
0119: */
0120: protected void changeAttributeValue(String typename,
0121: String elementXPath, final String attributeName,
0122: final String newValue) {
0123: Document doc = types.get(typename);
0124: int changes = XomHelper.query(doc, elementXPath,
0125: new ElementBlock() {
0126: public void use(Element typeElement) {
0127: typeElement.addAttribute(new Attribute(
0128: attributeName, newValue));
0129: }
0130: });
0131:
0132: if (changes == 0) {
0133: log.warn("** Changing " + elementXPath + " from "
0134: + typename + " didn't result in any changes");
0135: }
0136: }
0137:
0138: /**
0139: *
0140: */
0141: protected void applyPostProcessingHacks() {
0142: log
0143: .warn("jsx3.gui.TextBox.OVERFLOWSCROLL: Field is overridden from jsx3.gui.Block (with a different type), is this safe?");
0144: removeElement("jsx3.gui.TextBox",
0145: "/class/field[@name='OVERFLOWSCROLL']");
0146:
0147: log
0148: .warn("jsx3.gui.Block.OVERFLOWSCROLL: Field is overridden in jsx3.gui.TextBox (with a different type), is this safe?");
0149: removeElement("jsx3.gui.Block",
0150: "/class/field[@name='OVERFLOWSCROLL']");
0151:
0152: log
0153: .warn("jsx3.gui.Block.DEFAULTCLASSNAME: Field is overridden in several places, is this safe?");
0154: removeElement("jsx3.gui.Block",
0155: "/class/field[@name='DEFAULTCLASSNAME']");
0156:
0157: log
0158: .warn("jsx3.gui.Block.DEFAULTFONTSIZE: Field is overridden in jsx3.gui.WindowBar, is this safe?");
0159: removeElement("jsx3.gui.Block",
0160: "/class/field[@name='DEFAULTFONTSIZE']");
0161:
0162: log
0163: .warn("jsx3.chart.Chart.DEFAULT_FILLS: Ignoring, value has been trimmed to 1st line");
0164: removeElement("jsx3.chart.Chart",
0165: "/class/field[@name='DEFAULT_FILLS']");
0166:
0167: log
0168: .warn("jsx3.app.Model.META_FIELDS: Ignoring, value has been trimmed to 1st line");
0169: removeElement("jsx3.app.Model",
0170: "/class/field[@name='META_FIELDS']");
0171:
0172: log
0173: .warn("jsx3.gui.Sound.QUICKTIME: Ignoring, value has been trimmed to 1st line");
0174: removeElement("jsx3.gui.Sound",
0175: "/class/field[@name='QUICKTIME']");
0176:
0177: log
0178: .warn("jsx3.gui.Table.DEFAULT_CELL_VALUE_TEMPLATE: Ignoring, value has been trimmed to 1st line");
0179: removeElement("jsx3.gui.Table",
0180: "/class/field[@name='DEFAULT_CELL_VALUE_TEMPLATE']");
0181:
0182: // We don't warn for these because Drapgen is at fault
0183: removeElement("jsx3.chart.PointRenderer",
0184: "/interface/field[@name='CIRCLE']");
0185: removeElement("jsx3.chart.PointRenderer",
0186: "/interface/field[@name='CROSS']");
0187: removeElement("jsx3.chart.PointRenderer",
0188: "/interface/field[@name='DIAMOND']");
0189: removeElement("jsx3.chart.PointRenderer",
0190: "/interface/field[@name='BOX']");
0191: removeElement("jsx3.chart.PointRenderer",
0192: "/interface/field[@name='TRIANGLE']");
0193:
0194: log
0195: .warn("jsx3.gui.Stack.BORDER: ';' in value triggers end of line trimming");
0196: removeElement("jsx3.gui.Stack", "/class/field[@name='BORDER']");
0197:
0198: log
0199: .warn("jsx3.gui.WindowBar.DEFAULTBORDER: ';' in value triggers end of line trimming");
0200: removeElement("jsx3.gui.WindowBar",
0201: "/class/field[@name='DEFAULTBORDER']");
0202:
0203: log
0204: .warn("jsx3.gui.WindowBar.DEFAULTBORDERCAPTION: ';' in value triggers end of line trimming");
0205: removeElement("jsx3.gui.WindowBar",
0206: "/class/field[@name='DEFAULTBORDERCAPTION']");
0207: }
0208:
0209: /**
0210: * Utility to remove (an) element(s) by xpath selector from a type
0211: */
0212: protected void removeElement(String typename, String xpath) {
0213: Document doc = types.get(typename);
0214: int changes = XomHelper.query(doc, xpath, new ElementBlock() {
0215: public void use(Element element) {
0216: element.getParent().removeChild(element);
0217: }
0218: });
0219:
0220: if (changes == 0) {
0221: log.warn("** Removing " + xpath + " from " + typename
0222: + " didn't result in any changes");
0223: }
0224: }
0225:
0226: /**
0227: * Load the GI XML files into a map of loaded XOM documents
0228: * @param directory The source of GI XML files
0229: */
0230: protected void loadGiClasses(final File directory) {
0231: types = new HashMap<String, Document>();
0232:
0233: // Create a list of all the classes we need to generate
0234: FileSystemGuide guide = new FileSystemGuide(directory);
0235: guide.visit(new Visitor() {
0236: public void visitFile(File file) {
0237: try {
0238: if (!file.getAbsolutePath().endsWith(".xml")) {
0239: log.info("Skipping: " + file.getAbsolutePath());
0240: return;
0241: }
0242:
0243: String className = file.getAbsolutePath()
0244: .substring(
0245: directory.getAbsolutePath()
0246: .length() + 1)
0247: .replaceFirst("\\.xml$", "").replace("/",
0248: ".");
0249:
0250: Document doc = builder.build(file);
0251:
0252: types.put(className, doc);
0253: } catch (Exception ex) {
0254: throw new RuntimeException(ex);
0255: }
0256: }
0257:
0258: public boolean visitDirectory(File dir) {
0259: return true;
0260: }
0261: });
0262: }
0263:
0264: /**
0265: * Find the super class names listed in the given document.
0266: * Perhaps this should be cached somewhere???
0267: * @param doc The document to search in
0268: * @return The list of found super classes
0269: */
0270: protected List<String> getSuperClasses(Document doc) {
0271: List<String> super Classes = new ArrayList<String>();
0272: Nodes super ClassNodes = doc.query("/class/superclass/@name");
0273: for (int i = 0; i < super ClassNodes.size(); i++) {
0274: super Classes.add(super ClassNodes.get(i).getValue());
0275: }
0276: return super Classes;
0277: }
0278:
0279: /**
0280: * Copying mixin functions because Java does not do MI
0281: * @throws InterruptedException If the threading breaks
0282: */
0283: protected void copyMixinFunctions() throws InterruptedException {
0284: ExecutorService exec = Executors.newFixedThreadPool(2);
0285:
0286: for (final Map.Entry<String, Document> entry : types.entrySet()) {
0287: exec.execute(new Runnable() {
0288: public void run() {
0289: String className = entry.getKey();
0290: Document doc = entry.getValue();
0291:
0292: // Search the XML for lines like this from Button.xml:
0293: // <implements direct="1" id="implements:0" loaded="1" name="jsx3.gui.Form"/>
0294:
0295: // Find all the inherited functions, and trim the ones that are not
0296: // from super-classes - leaving the mixins
0297: Nodes mixinRefNodes = doc
0298: .query("/class/method[@inherited='1']");
0299:
0300: for (int i = 0; i < mixinRefNodes.size(); i++) {
0301: Element mixinRefNode = (Element) mixinRefNodes
0302: .get(i);
0303: String name = mixinRefNode
0304: .getAttributeValue("name");
0305: if (name == null) {
0306: throw new NullPointerException(
0307: "Missing name attribute from element: "
0308: + mixinRefNode);
0309: }
0310:
0311: // We might be implemented elsewhere
0312: String mixinSource = mixinRefNode
0313: .getAttributeValue("source");
0314: if (mixinSource == null) {
0315: mixinSource = className;
0316: }
0317:
0318: boolean isMixin = true;
0319: for (String super Class : getSuperClasses(doc)) {
0320: if (mixinSource.equals(super Class)) {
0321: isMixin = false;
0322: }
0323: }
0324:
0325: if (isMixin) {
0326: // replace the method element with the corresponding element from Form.
0327: if (!"jsx3.lang.Object".equals(mixinSource)) {
0328: Document mixinSourceDocument = types
0329: .get(mixinSource);
0330: Nodes templateNodes = mixinSourceDocument
0331: .query("/interface/method[@name='"
0332: + name + "']");
0333: if (templateNodes.size() != 1) {
0334: log
0335: .warn("** Found "
0336: + templateNodes
0337: .size()
0338: + " methods called "
0339: + name
0340: + " in "
0341: + mixinSource
0342: + " as a result of reference from "
0343: + className);
0344: } else {
0345: Element templateNode = (Element) templateNodes
0346: .get(0);
0347: Node replacementNode = templateNode
0348: .copy();
0349: mixinRefNode.getParent()
0350: .replaceChild(mixinRefNode,
0351: replacementNode);
0352: }
0353: }
0354: }
0355: }
0356: }
0357: });
0358: }
0359: exec.shutdown();
0360: exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
0361: }
0362:
0363: /**
0364: * Remove functions implemented by parent
0365: */
0366: protected void trimDuplicateMethods() {
0367: SortedSet<String> keys = new TreeSet<String>(types.keySet());
0368: for (final String className : keys) {
0369: Document doc = types.get(className);
0370:
0371: // Get a list of super classes
0372: final List<Document> parents = new ArrayList<Document>();
0373: for (String super ClassName : getSuperClasses(doc)) {
0374: Document parent = types.get(super ClassName);
0375: if (parent == null) {
0376: throw new IllegalStateException(
0377: "Unknown superclass: " + super ClassName);
0378: }
0379: parents.add(parent);
0380: }
0381:
0382: // Find all the inherited functions, and trim the ones that are not
0383: // from super-classes - leaving the mixins
0384: XomHelper.query(doc, "/class/method[not(@inherited)]",
0385: new ElementBlock() {
0386: public void use(final Element inherited) {
0387: final String inheritedName = inherited
0388: .getAttributeValue("name");
0389:
0390: // Loop over signatures, checking for methods in super-classes
0391: for (Document parent : parents) {
0392: try {
0393: XomHelper
0394: .query(
0395: parent,
0396: "/class/method[not(@inherited)]",
0397: new ElementBlock() {
0398: public void use(
0399: Element parentMethod) {
0400: if (inherited
0401: .getParent() == null) {
0402: return;
0403: }
0404:
0405: String parentMethodName = parentMethod
0406: .getAttributeValue("name");
0407: if (inheritedName
0408: .equals(parentMethodName)) {
0409: inherited
0410: .getParent()
0411: .removeChild(
0412: inherited);
0413: }
0414: }
0415: });
0416: } catch (XPathException ex) {
0417: log.warn("** Failed XPath for '"
0418: + className + "': "
0419: + ex.getXPath());
0420: }
0421: }
0422: }
0423: });
0424: }
0425: }
0426:
0427: /* (non-Javadoc)
0428: * @see org.directwebremoting.drapgen.loader.Loader#loadToProject(org.directwebremoting.drapgen.ast.Project)
0429: */
0430: public void loadToProject(final Project project) {
0431: SortedSet<String> keys = new TreeSet<String>(types.keySet());
0432: for (String className : keys) {
0433: String rename = hasLocalVersion(className);
0434: if (rename != null) {
0435: log
0436: .debug(className
0437: + ": Skipping: has a local version - "
0438: + rename);
0439: continue;
0440: }
0441:
0442: if (skipOnInput(className)) {
0443: log.debug(className
0444: + ": Skipping: marked skip on input");
0445: continue;
0446: }
0447:
0448: // Remove documentation classes like index.java, package-summary.java, etc
0449: if (className.endsWith("index")
0450: || className.endsWith("inheritance")
0451: || className.endsWith("package-summary")) {
0452: log.debug(className + ": Skipping: it's documentation");
0453: continue;
0454: }
0455:
0456: Element root = types.get(className).getRootElement();
0457: if (root.getAttributeValue("deprecated") != null) {
0458: deprecatedTypes.add(className);
0459: log.debug(className + ": Skipping: it's deprecated");
0460: continue;
0461: }
0462:
0463: final Type type = getType(project, className);
0464: String typeDocs = readDocumentation(root);
0465: type.setDocumentation(typeDocs);
0466:
0467: String super ClassName = XomHelper.queryValue(root,
0468: "superclass[@direct='1']/@name");
0469: if (super ClassName == null) {
0470: type
0471: .setSuperClass(getType(project,
0472: "jsx3.lang.Object"));
0473: } else {
0474: type.setSuperClass(getType(project, super ClassName));
0475: }
0476:
0477: XomHelper.getChildElements(root, "implements",
0478: new ImplementsElementBlock(project, type));
0479: XomHelper.getChildElements(root, "constructor",
0480: new ConstructorElementBlock(project, type));
0481: XomHelper.getChildElements(root, "method",
0482: new MethodElementBlock(project, type));
0483: XomHelper.getChildElements(root, "field",
0484: new FieldElementBlock(project, type));
0485: }
0486:
0487: // We need to remove the classes that we don't want to generate
0488: Set<Type> toDelete = new HashSet<Type>();
0489: types: for (Type type : project.getTypes()) {
0490: if (!type.getPackageName().startsWith("jsx3")) {
0491: toDelete.add(type);
0492: continue types;
0493: }
0494: for (String manualClassName : SKIP_ON_OUTPUT) {
0495: if (type.getFullName().equals(manualClassName)) {
0496: toDelete.add(type);
0497: continue types;
0498: }
0499: }
0500: }
0501: for (Type type : toDelete) {
0502: project.remove(type);
0503: }
0504: }
0505:
0506: /**
0507: * Find all the constants
0508: */
0509: protected class FieldElementBlock extends ProjectElementBlock {
0510: protected FieldElementBlock(Project project, Type type) {
0511: super (project, type);
0512: }
0513:
0514: public void use(Element fieldElement) {
0515: if (fieldElement.getAttribute("deprecated") != null) {
0516: return;
0517: }
0518:
0519: String statc = fieldElement.getAttributeValue("static");
0520: if (statc == null || !statc.equals("1")) {
0521: return;
0522: }
0523:
0524: String access = fieldElement.getAttributeValue("access");
0525: if (access == null || !access.equals("public")) {
0526: return;
0527: }
0528:
0529: String name = fieldElement.getAttributeValue("name");
0530:
0531: Field field = type.createConstant();
0532: field.setDocumentation(stripToNull(fieldElement
0533: .getAttributeValue("text")));
0534: field.setDocumentation(readDocumentation(fieldElement));
0535: field.setName(name);
0536: field.setValue(fieldElement.getAttributeValue("value"));
0537:
0538: Elements typeElements = fieldElement
0539: .getChildElements("type");
0540: if (typeElements.size() == 0) {
0541: log
0542: .warn(type.getFullName()
0543: + "."
0544: + name
0545: + "(): Missing type element for constant. Using Object.");
0546: field.setType(getType(project, "java.lang.Object"));
0547: } else {
0548: if (typeElements.size() > 1) {
0549: log
0550: .warn(type.getFullName()
0551: + "."
0552: + name
0553: + "(): Multiple type element for a constant!. Using first.");
0554: }
0555:
0556: Element typeElement = typeElements.get(0);
0557: String typeName = typeElement.getAttributeValue("name");
0558: field.setType(getType(project, typeName));
0559: }
0560: }
0561: }
0562:
0563: /**
0564: * Find all the methods that we're interested in
0565: */
0566: protected class MethodElementBlock extends ProjectElementBlock {
0567: protected MethodElementBlock(Project project, Type type) {
0568: super (project, type);
0569: }
0570:
0571: public void use(Element methodElement) {
0572: if (methodElement.getAttribute("deprecated") != null) {
0573: return;
0574: }
0575:
0576: if (methodElement.getAttribute("inherited") != null) {
0577: return;
0578: }
0579:
0580: String methodName = methodElement.getAttributeValue("name");
0581: String debugName = type.getFullName() + "." + methodName;
0582:
0583: String documentation = readDocumentation(methodElement);
0584: Parameter returnType = readGiReturnType(methodElement,
0585: project, debugName);
0586:
0587: // Read a set of param elements attached to a constructor or method
0588:
0589: Set<List<Parameter>> parameters = getGiParamList(
0590: methodElement, debugName, project);
0591: for (List<Parameter> parameter : parameters) {
0592: Method method = type.createMethod();
0593: method.setDocumentation(documentation);
0594: method.setName(methodName);
0595: method.setReturnType(returnType);
0596: method.setParameters(parameter);
0597: }
0598: }
0599: }
0600:
0601: /**
0602: * Find all the constructors that we're interested in
0603: */
0604: protected class ConstructorElementBlock extends ProjectElementBlock {
0605: protected ConstructorElementBlock(Project project, Type type) {
0606: super (project, type);
0607: }
0608:
0609: public void use(Element ctorElement) {
0610: if (ctorElement.getAttribute("deprecated") != null) {
0611: return;
0612: }
0613:
0614: // Read a set of param elements attached to a constructor or method
0615:
0616: String documentation = readDocumentation(ctorElement);
0617: Set<List<Parameter>> parameters = getGiParamList(
0618: ctorElement, type.getFullName() + ".ctor", project);
0619: for (List<Parameter> parameter : parameters) {
0620: Constructor constructor = type.createConstructor();
0621: constructor.setDocumentation(documentation);
0622: constructor.setParameters(parameter);
0623: }
0624: }
0625: }
0626:
0627: /**
0628: * Helper so we can implement ElementBlock easily
0629: */
0630: protected abstract class ProjectElementBlock implements
0631: ElementBlock {
0632: /**
0633: * @param project The project that we are writing to
0634: * @param type The type we are creating
0635: */
0636: protected ProjectElementBlock(Project project, Type type) {
0637: this .project = project;
0638: this .type = type;
0639: }
0640:
0641: /**
0642: * The project that we are writing to
0643: */
0644: protected final Project project;
0645:
0646: /**
0647: * The type we are creating
0648: */
0649: protected final Type type;
0650: }
0651:
0652: /**
0653: * Process an implements element
0654: */
0655: protected class ImplementsElementBlock extends ProjectElementBlock {
0656: protected ImplementsElementBlock(Project project, Type type) {
0657: super (project, type);
0658: }
0659:
0660: public void use(Element implements Element) {
0661: String direct = implements Element
0662: .getAttributeValue("direct");
0663: if (direct == null || !direct.equals("1")) {
0664: return;
0665: }
0666:
0667: String interfaceName = implements Element
0668: .getAttributeValue("name");
0669: if (interfaceName == null) {
0670: log
0671: .warn(type.getFullName()
0672: + ": Skipping direct implements element with no name");
0673: }
0674: type.addInterface(getType(project, interfaceName));
0675: }
0676: }
0677:
0678: /**
0679: * We want to alter the names of some classes as we generate them
0680: * @param project The project that we use to lookup Types
0681: * @param className The name of the requested class
0682: * @return A Type representing the name altered type
0683: */
0684: protected Type getType(Project project, String className) {
0685: if (isDeprecated(className)) {
0686: log.warn("** Request for deprecated type: " + className);
0687: return project.getType("jsx3.lang.Unavailable");
0688: }
0689:
0690: String name = checkAlternativeNames(className);
0691:
0692: return project.getType(name);
0693: }
0694:
0695: /**
0696: * @param className The name to check for deprecation
0697: * @return true if the type has been deprecated
0698: */
0699: protected boolean isDeprecated(String className) {
0700: for (String deprecatedType : deprecatedTypes) {
0701: if (className.equals(deprecatedType)) {
0702: return true;
0703: }
0704: }
0705:
0706: return false;
0707: }
0708:
0709: /**
0710: * We might need to use differing versions of class names
0711: */
0712: protected String checkAlternativeNames(String className) {
0713: for (String[] pairs : JSX3_RENAMES) {
0714: if (className.equals(pairs[0])) {
0715: return pairs[1];
0716: }
0717: }
0718: for (String[] pairs : HAS_LOCAL_VERSION) {
0719: if (className.equals(pairs[0])) {
0720: return pairs[1];
0721: }
0722: }
0723: return className;
0724: }
0725:
0726: /**
0727: * Does the given string have a local version?
0728: */
0729: protected String hasLocalVersion(String className) {
0730: for (String[] pairs : HAS_LOCAL_VERSION) {
0731: if (className.equals(pairs[0])) {
0732: return pairs[1];
0733: }
0734: }
0735: return null;
0736: }
0737:
0738: /**
0739: * Does the given string have a local version?
0740: */
0741: protected boolean skipOnInput(String className) {
0742: for (String skip : SKIP_ON_INPUT) {
0743: if (className.equals(skip)) {
0744: return true;
0745: }
0746: }
0747: return false;
0748: }
0749:
0750: /**
0751: * We might want to move jsx3 classes around the hierarchy, mostly this
0752: * is due to lack of current support for inner classes
0753: */
0754: private static final String[][] JSX3_RENAMES = new String[][] {
0755: { "jsx3.gui.Matrix.BlockMask", "jsx3.gui.matrix.BlockMask" },
0756: { "jsx3.gui.Matrix.Column", "jsx3.gui.matrix.Column" },
0757: { "jsx3.gui.Matrix.ColumnFormat",
0758: "jsx3.gui.matrix.ColumnFormat" },
0759: { "jsx3.gui.Matrix.EditMask", "jsx3.gui.matrix.EditMask" }, };
0760:
0761: /**
0762: * JSX classes that we don't have a local version of, but we ignore anyway
0763: */
0764: private static final String[] SKIP_ON_INPUT = new String[] {
0765: "jsx3.app.Monitor", "jsx3.app.PropsBundle",
0766: "jsx3.lang.NativeError", "jsx3.lang.Package",
0767: "jsx3.net.URIResolver", "jsx3.util.Logger",
0768: "jsx3.util.Logger.AlertHandler",
0769: "jsx3.util.Logger.FormatHandler",
0770: "jsx3.util.Logger.Handler", "jsx3.util.Logger.Manager",
0771: "jsx3.util.Logger.MemoryHandler",
0772: "jsx3.util.Logger.Record", "jsx3.xml.XslDocument", };
0773:
0774: /**
0775: * Many JSX classes are not converted because there is a better local
0776: * version.
0777: */
0778: private static final String[][] HAS_LOCAL_VERSION = new String[][] {
0779: { "", "jsx3.lang.Object" },
0780: { "Object", "jsx3.lang.Object" },
0781: { "Date", "java.util.Date" },
0782: { "Iterator", "java.util.Iterator" },
0783: { "Math", "java.lang.Math" },
0784: { "RegExp", "java.util.regex.Pattern" },
0785: { "Number", "Integer" },
0786: { "String", "String" },
0787: { "Array", "Object[]" },
0788: { "Boolean", "Boolean" },
0789: { "Function", "org.directwebremoting.proxy.CodeBlock" },
0790: { "HTMLElement", "String" },
0791: { "HTMLDocument", "String" },
0792: { "VectorStroke", "String" },
0793: { "jsx3.Boolean", "Boolean" },
0794: { "jsx3.app.Locale", "java.util.Locale" },
0795: { "jsx3.app.Properties", "java.util.Properties" },
0796: { "jsx3.lang.Class", "Class" },
0797: { "jsx3.lang.ClassLoader", "ClassLoader" },
0798: { "jsx3.lang.Exception", "Exception" },
0799: { "jsx3.lang.IllegalArgumentException",
0800: "IllegalArgumentException" },
0801: { "jsx3.lang.Method", "java.lang.reflect.Method" },
0802: { "jsx3.net.URI", "java.net.URI" },
0803: { "jsx3.util.DateFormat", "java.util.DateFormat" },
0804: { "jsx3.util.Locale", "java.util.Locale" },
0805: { "jsx3.util.List", "java.util.List" },
0806: { "jsx3.util.Iterator", "java.util.Iterator" },
0807: { "jsx3.util.MessageFormat", "java.util.MessageFormat" },
0808: { "jsx3.util.NumberFormat", "java.util.NumberFormat" },
0809: { "jsx3.xml.CDF", "jsx3.xml.CdfDocument" },
0810: { "jsx3.xml.CDF.Document", "jsx3.xml.CdfDocument" },
0811: { "jsx3.xml.Document", "jsx3.xml.CdfDocument" },
0812: { "jsx3.xml.Entity", "jsx3.xml.Node" }, };
0813:
0814: /**
0815: * Some JSX3 classes have been hand cranked, or should not be generated
0816: */
0817: private static final String[] SKIP_ON_OUTPUT = new String[] {
0818: "jsx3.lang.Object", "jsx3.lang.Unavailable",
0819: "jsx3.net.URIResolver", "jsx3.xml.CdfDocument",
0820: "jsx3.xml.Node", };
0821:
0822: /**
0823: * Read the text child element and set the drapgen AST element with it's
0824: * content
0825: * @param xomElement the XML element to read from
0826: */
0827: protected String readDocumentation(Element xomElement) {
0828: Element textElement = xomElement.getFirstChildElement("text");
0829: if (textElement != null) {
0830: return stripToNull(textElement.getValue());
0831: } else {
0832: return null;
0833: }
0834: }
0835:
0836: /**
0837: * Read a set of param elements attached to a constructor or method
0838: * @param element The XOM element to read from
0839: * @param debugName
0840: * @param project
0841: * @return A list of found parameters
0842: */
0843: protected Set<List<Parameter>> getGiParamList(Element element,
0844: final String debugName, final Project project) {
0845: // The types used by this method are somewhat funky. We first create a
0846: // list of parameters with the alternative types for each in a stack.
0847: // We then need to return a set where all the different combinations of
0848: // these alternatives are represented. We use Set<List<>> and
0849: // List<Stack<>> to help make it clearer what we are working on.
0850:
0851: // So GI has a list of parameters for this subroutine. Each parameter
0852: // has a set of types that it can be
0853: final List<Stack<Parameter>> parametersWithOptions = new ArrayList<Stack<Parameter>>();
0854: XomHelper.getChildElements(element, "param",
0855: new ElementBlock() {
0856: public void use(Element paramElement) {
0857: String documentation = stripToNull(paramElement
0858: .getAttributeValue("text"));
0859: String name = paramElement
0860: .getAttributeValue("name");
0861:
0862: Set<Type> alternatives = new HashSet<Type>();
0863:
0864: Elements typeElements = paramElement
0865: .getChildElements("type");
0866: if (typeElements.size() == 0) {
0867: String typeName = "java.lang.Object";
0868: if (name.startsWith("str")) {
0869: typeName = "String";
0870: } else if (name.startsWith("b")) {
0871: typeName = "boolean";
0872: } else if (name.startsWith("int")) {
0873: typeName = "int";
0874: }
0875: log
0876: .warn(debugName
0877: + "(): Missing type element for parameter '"
0878: + name + "'. Guessing at '"
0879: + typeName
0880: + "' from parameter name.");
0881: alternatives
0882: .add(getType(project, typeName));
0883: } else {
0884: for (int i = 0; i < typeElements.size(); i++) {
0885: Element typeElement = typeElements
0886: .get(i);
0887: String typeName = typeElement
0888: .getAttributeValue("name");
0889: if (typeName.equals("function")) {
0890: typeName = "Function";
0891: log
0892: .warn(debugName
0893: + "(): Types of parameter '"
0894: + name
0895: + "' includes 'function', it probably should be 'Function'");
0896: }
0897: alternatives.add(getType(project,
0898: typeName));
0899: }
0900: }
0901:
0902: Stack<Parameter> p = new Stack<Parameter>();
0903: for (Type alternative : alternatives) {
0904: Parameter parameter = new Parameter(project);
0905: parameter.setDocumentation(documentation);
0906: parameter.setName(name);
0907: parameter.setType(alternative);
0908: p.add(parameter);
0909: }
0910:
0911: parametersWithOptions.add(p);
0912: }
0913: });
0914:
0915: Set<List<Parameter>> parameterCombinations = new HashSet<List<Parameter>>();
0916:
0917: if (parametersWithOptions.size() > 0) {
0918: recurseOverTypesInParameter(parametersWithOptions, 0,
0919: new LinkedList<Integer>(), parameterCombinations);
0920: } else {
0921: // There is one combination - that with no parameters
0922: parameterCombinations.add(new ArrayList<Parameter>());
0923: }
0924:
0925: return parameterCombinations;
0926: }
0927:
0928: /**
0929: * @param parametersWithOptions
0930: * @param paramNumber
0931: * @param combinations
0932: */
0933: private void recurseOverTypesInParameter(
0934: List<Stack<Parameter>> parametersWithOptions,
0935: int paramNumber, LinkedList<Integer> typeList,
0936: Set<List<Parameter>> combinations) {
0937: // We do *something* for each of the alternatives in the set of
0938: // parameters for this type
0939: Stack<Parameter> alternatives = parametersWithOptions
0940: .get(paramNumber);
0941: for (int i = 0; i < alternatives.size(); i++) {
0942: LinkedList<Integer> nextTypeList = new LinkedList<Integer>(
0943: typeList);
0944: nextTypeList.add(i);
0945:
0946: if (paramNumber == parametersWithOptions.size() - 1) {
0947: // If this is the last parameter then *something* is creating
0948: // an entry in the output set
0949: List<Parameter> combination = new ArrayList<Parameter>();
0950: for (int paramIndex = 0; paramIndex < nextTypeList
0951: .size(); paramIndex++) {
0952: Integer selection = nextTypeList.get(paramIndex);
0953: Parameter selected = parametersWithOptions.get(
0954: paramIndex).get(selection);
0955: combination.add(selected);
0956: }
0957: combinations.add(combination);
0958: } else {
0959: // If this is not the last parameter then *something* is to call
0960: // ourself to loop over the options for the next parameter
0961: recurseOverTypesInParameter(parametersWithOptions,
0962: paramNumber + 1, nextTypeList, combinations);
0963: }
0964: }
0965: }
0966:
0967: /**
0968: * Read a return type element attached to a method.
0969: * For some reason calling method.toString as part of the log.warn messages
0970: * crashes the VM on mac-os. Why???
0971: * @param element The XOM element to read from
0972: */
0973: protected Parameter readGiReturnType(Element element,
0974: Project project, final String name) {
0975: Parameter returnType = new Parameter(project);
0976:
0977: //Project project = method.getParent().getProject();
0978: Elements returnElements = element.getChildElements("return");
0979: if (returnElements.size() == 0) {
0980: returnType.setType(getType(project, "void"));
0981: return returnType;
0982: } else {
0983: if (returnElements.size() > 1) {
0984: log
0985: .warn(name
0986: + "(): More than one return element. Using first.");
0987: }
0988:
0989: Element returnElement = returnElements.get(0);
0990:
0991: String documentation = returnElement
0992: .getAttributeValue("text");
0993: returnType.setDocumentation(stripToNull(documentation));
0994:
0995: Elements typeElements = returnElement
0996: .getChildElements("type");
0997: if (typeElements.size() == 0) {
0998: log.warn(name
0999: + "(): Missing return element. Using Object.");
1000: Type type = getType(project, "java.lang.Object");
1001: returnType.setType(type);
1002: return returnType;
1003: } else {
1004: if (typeElements.size() > 1) {
1005: log
1006: .warn(name
1007: + "(): More than one type element in return. Using first. Are the semantics of this clear?");
1008: }
1009:
1010: Element typeElement = typeElements.get(0);
1011: String typeName = typeElement.getAttributeValue("name");
1012: Type type = getType(project, typeName);
1013: returnType.setType(type);
1014: return returnType;
1015: }
1016: }
1017: }
1018:
1019: /**
1020: * Utility for simplifying documentation
1021: * @param input The string to call {@link String#trim()} on before swapping
1022: * empty replies for null
1023: * @return A non blank, trimmed string, possibly null
1024: */
1025: public static String stripToNull(String input) {
1026: if (input == null) {
1027: return null;
1028: }
1029: String output = input.trim();
1030: if (output.length() == 0) {
1031: return null;
1032: }
1033: return output;
1034: }
1035:
1036: /**
1037: * What classes have been deprecated?
1038: */
1039: protected Set<String> deprecatedTypes = new HashSet<String>();
1040:
1041: /**
1042: * Our cache of loaded GI documents
1043: */
1044: protected Map<String, Document> types;
1045:
1046: /**
1047: * The XOM document builder
1048: */
1049: protected Builder builder = new Builder();
1050:
1051: /**
1052: * The log stream
1053: */
1054: protected static final Logger log = Logger
1055: .getLogger(GiLoader.class);
1056: }
|