001: package com.xoetrope.builder.pojo;
002:
003: import com.xoetrope.builder.generic.XAttributeMapping;
004: import com.xoetrope.builder.generic.XComponentMapping;
005: import com.xoetrope.builder.generic.XGenericBuilder;
006: import com.xoetrope.builder.generic.XInstructionMapping;
007: import com.xoetrope.builder.generic.XTypeMapping;
008: import com.xoetrope.carousel.build.BuildProperties;
009: import java.awt.Component;
010: import java.awt.Container;
011: import java.beans.BeanInfo;
012: import java.beans.IntrospectionException;
013: import java.beans.Introspector;
014: import java.beans.PropertyDescriptor;
015: import java.io.Reader;
016: import java.util.ArrayList;
017: import java.util.Hashtable;
018: import java.util.Stack;
019: import net.xoetrope.debug.DebugLogger;
020: import net.xoetrope.optional.layout.ColumnLayout;
021: import net.xoetrope.xml.XmlElement;
022: import net.xoetrope.xui.PageSupport;
023: import net.xoetrope.xui.XPage;
024: import net.xoetrope.xui.XProject;
025:
026: /**
027: * <p>
028: * A form builder based upon the GenericBuilder. The builder processes mappings
029: * using reflection to inspect the available properties of a POJO and
030: * creating a form in the process.
031: * </p>
032: * <p> Copyright (c) Xoetrope Ltd., 2001-2007, This software is licensed under
033: * the GNU Public License (GPL), please see license.txt for more details. If
034: * you make commercial use of this software you must purchase a commercial
035: * license from Xoetrope.
036: * </p>
037: */
038: public class XPojoFormBuilder extends XGenericBuilder {
039: protected Class pojoClass;
040: protected Object pojo;
041:
042: protected BeanInfo beanInfo;
043: protected PropertyDescriptor[] properties;
044:
045: /**
046: * Create a new builder. An attempt is made to configure the builder with a
047: * configuration file when the builder is constructed if the configuration file is
048: * specified as part of the startup properties via the
049: * <code>XGenericBuilderConfig</code> setting.
050: * @param project the current xui project
051: * @param factory the component factory
052: */
053: public XPojoFormBuilder(XProject project) {
054: super (project);
055: }
056:
057: /**
058: * Loads an XPage via a reader obtained from the XProject (searches
059: * the classpath). The pageName is assumed to be the name of an Java POJO
060: * class file. For example if the pageName is 'welcome' then the
061: * 'Welcome.class' file in the default package is inspected. A full class name
062: * can be specified.
063: * @param defPackageName the package or path to the page
064: * @param pageName the page name or the name of the class implementing the page
065: * @param include true if the page to be loaded is being included in another
066: * page in which case any class attribute of the included page is ignored
067: * @return the page
068: */
069: public PageSupport loadPage(String defPackageName, String pageName,
070: boolean include) {
071: try {
072: String className = pageName;
073: pojoClass = Class.forName(className.trim());
074: pojo = pojoClass.newInstance();
075: } catch (Exception ex) {
076: return null;
077: }
078:
079: try {
080: PageSupport formPage;
081: if (!include)
082: formPage = readPage(null, pageName, fileExtension,
083: include);
084: else {
085: if (layoutManager instanceof ColumnLayout)
086: ((ColumnLayout) layoutManager).setIndentY(30);
087: formPage = super .readPage(null, pageName,
088: fileExtension, include);
089: }
090:
091: if (!include) {
092: String decorationPage;
093: int pos = pageName.indexOf('.');
094: if (pos > 0)
095: decorationPage = pageName.substring(0, pos);
096: else
097: decorationPage = pageName;
098: decorationPage += "Decoration.xml";
099: componentFactory.setParentComponent(decorationPanel);
100: loadPage(packageName, decorationPage, true);
101: }
102:
103: return formPage;
104: } catch (Exception e) {
105: if (BuildProperties.DEBUG)
106: DebugLogger.logError("BUILDER", "File NOT found: "
107: + pageName);
108: } finally {
109: if (!include)
110: rootPage = null;
111: }
112:
113: return null;
114: }
115:
116: /**
117: * Read an XML description of the page and construct a new XPage. An instance
118: * of the class specified by the class attribute is constructed or else an
119: * instance of XPage if no class attribute is specified. The new page is
120: * populated but is not yet added to its parent.
121: * <br>
122: * The startup file parameter 'DefaultClass' is used to obtain a default for
123: * each page's class if a class parameter is not specified in the page's XML
124: * <br>
125: * The startup file parameter 'Validations' is used to obtain a default for
126: * each page's set of validation rules
127: *
128: * @param reader a input stream from which to read the page
129: * @param pageName the name of the page
130: * @param ext the file extension
131: * @param include the page to be loaded is being included in another page
132: * @return the page
133: */
134: public PageSupport readPage(Reader reader, String pageName,
135: String ext, boolean include) {
136: setupPage(null, pageName, ext, include);
137: eventHandler = page.getEventHandler();
138: //
139: // Vector componentNodes = model.getChildren();
140: // int numChildren = componentNodes.size();
141: // for ( int i = 0; i < numChildren; i++ ) {
142: // XmlElement childNode = ( XmlElement )componentNodes.elementAt( i );
143: // String typeStr = childNode.getName().toLowerCase();
144: // if ( typeStr.equals( componentsNodeName ))
145: // componentsNode = childNode;
146: // else if ( typeStr.equals( "events" ))
147: // eventsNode = childNode;
148: // else if ( typeStr.equals( "vocabularies" ))
149: // vocabNode = childNode;
150: // else if ( typeStr.equals( "validations" ))
151: // validationsNode = childNode;
152: // else if ( typeStr.equals( "attributes" ))
153: // loadAttributeSet( childNode );
154: // else if ( typeStr.equals( "include" )) {
155: // loadPage( packageName, childNode.getAttribute( "file" ), true );
156: // componentFactory.setParentComponent( page );
157: // }
158: // else if ( typeStr.equals( "scripts" ))
159: // scriptsNode = childNode;
160: // else
161: // loadOtherElement( page, childNode );
162: // }
163: //
164: // if ( vocabNode != null )
165: // addVocabulary( page, vocabNode );
166: //
167: // if ( componentsNode != null ) {
168: // setupPageLayout( componentsNode );
169: addComponents((include ? componentFactory.getParentComponent()
170: : formPanel), null);
171: // }
172: //
173: // if ( eventsNode != null )
174: // addEvents( page, eventsNode );
175: //
176: // if ( validationsNode != null )
177: // addValidations( page, validationsNode );
178: //
179: // if ( scriptsNode != null )
180: // addScripts( page, scriptsNode );
181:
182: page.validate();
183: return page;
184: }
185:
186: /**
187: * Loads the page based on the contents of the page tag or by using default
188: * values.
189: *
190: * @param pageName the name of the page
191: * @param ext the file extension
192: * @param include the page to be loaded is being included in another page
193: */
194: protected void setupPage(XmlElement model, String pageName,
195: String ext, boolean include) {
196: if (!include) {
197: page = new XPage();
198: setPageName(pageName);
199: setPageExtension(ext);
200: page.setLayout(new ColumnLayout(2, 8));
201:
202: componentFactory.setParentComponent((Container) page);
203: } else
204: super .setupPage(model, pageName, ext, include);
205:
206: rootPage = (XPage) page;
207: }
208:
209: // Type handling -------------------------------------------------------------
210: /**
211: * Adds the elements specified by the Components element and its children
212: * @param page the new page object
213: * @param model the Components XML element (and implicitly its children)
214: */
215: protected void addComponents(Object page, XmlElement model) {
216: try {
217: // componentFactory.setParentComponent( (Container)page );
218:
219: beanInfo = Introspector.getBeanInfo(pojoClass);
220: properties = beanInfo.getPropertyDescriptors();
221: int numProperties = properties.length;
222: for (int i = 0; i < numProperties; i++)
223: addComponent(properties[i]);
224: } catch (IntrospectionException ex) {
225: ex.printStackTrace();
226: }
227:
228: // Vector componentNodes = model.getChildren();
229: // int numChildren = componentNodes.size();
230: // for ( int i = 0; i < numChildren; i++ ) {
231: // XmlElement childNode = ( XmlElement )componentNodes.elementAt( i );
232: // if ( childNode.getName().compareTo( "Repeat" ) != 0 )
233: // addComponent( childNode );
234: // else {
235: // String repeatReference = childNode.getAttribute( "while" );
236: // pushRepeatReference( repeatReference );
237: // // Repeat the child addition.
238: // boolean whileClauseResult = ((Boolean)rootPage.evaluateAttribute( repeatReference )).booleanValue();
239: // if ( whileClauseResult ) {
240: // i--;
241: // addComponents( page, childNode );
242: // }
243: // popRepeatReference( repeatReference );
244: // }
245: // }
246: }
247:
248: /**
249: * Adds an individual component element to the page (this method may be called
250: * recursively for nested elements). Several methods will be attempted until a
251: * component is successfully created. Firstly the built-in component types are
252: * checked, then any additional registered component constructors. The types
253: * can be specified by type ID, type name or class name.
254: * @param childNode the XML element containing the component specification.
255: * @return the new component
256: */
257: protected Object addComponent(PropertyDescriptor pd) {
258: String childName = pd.getName();
259: String childType = pd.getPropertyType().getName();
260:
261: try {
262: Hashtable components = new Hashtable();
263: Object mapping = typeMap.get(childType);
264: if (mapping == null)
265: mapping = typeMap.get(childName);
266:
267: if (mapping != null) {
268: if (mapping instanceof XComponentMapping) {
269: XComponentMapping componentMapping = (XComponentMapping) mapping;
270: Stack spans = new Stack();
271: int currentSpan = 0;
272: ArrayList componentTypes = componentMapping
273: .getComponentTypes();
274: int numTypes = componentTypes.size();
275: for (int i = 0; i < numTypes; i++) {
276: XTypeMapping tm = (XTypeMapping) componentTypes
277: .get(i);
278: Component c = (Component) componentFactory
279: .addComponent(tm.typeName, null, null);
280: currentSpan--;
281: if ((currentSpan == 0) && (spans.size() > 0)) {
282: currentSpan = ((Integer) spans.pop())
283: .intValue();
284: componentFactory
285: .setParentComponent(((Component) componentFactory
286: .getParentComponent())
287: .getParent());
288: }
289:
290: if (tm.span > 0) {
291: spans.push(new Integer(currentSpan));
292: componentFactory.setParentComponent(c);
293: currentSpan = tm.span;
294: }
295:
296: if (c != null)
297: components.put(tm.name, c);
298: else {
299: if (BuildProperties.DEBUG)
300: DebugLogger
301: .logError("Cannot instantiate type: "
302: + childName);
303: }
304: }
305:
306: ArrayList attributeMapping = componentMapping
307: .getAttributeMapping();
308: if (attributeMapping != null) {
309: int numMappings = attributeMapping.size();
310: for (int ii = 0; ii < numMappings; ii++) {
311: XAttributeMapping am = (XAttributeMapping) attributeMapping
312: .get(ii);
313: Component c = (Component) components.get(am
314: .getComponentRef());
315: String valueStr;
316: // If the source attribute is not specified the attribute should be
317: // applied anyhow - or always, as a default, for example specifying
318: // that a component is opaque
319: if (am.getSrcAttrib() != null)
320: valueStr = getMappedValue(getProperty(
321: am.getSrcAttrib()).getName(),
322: am.getValue());
323: else
324: valueStr = getMappedValue(c, am
325: .getValue());
326:
327: if (valueStr != null) {
328: // The name has a special role so we must set it before anything else
329: if (am.getDestAttrib().equals("name"))
330: c.setName(valueStr);
331: else
332: setComponentAttributes(c, am
333: .getDestAttrib(), valueStr,
334: null);
335: }
336: }
337:
338: // Now try the common attibutes, for example a style attribute that could
339: // be specified for any component
340: int amis = commonAttributeMapping.size();
341: for (int ami = 0; ami < amis; ami++) {
342: XAttributeMapping am = (XAttributeMapping) commonAttributeMapping
343: .get(ami);
344: Component c = (Component) components.get(am
345: .getComponentRef());
346: if (c != null) {
347: String valueStr;
348: // If the source attribute is not specified the attribute should be
349: // applied anyhow - or always, as a default, for example specifying
350: // that a component is opaque
351: if (am.getSrcAttrib() != null)
352: valueStr = getMappedValue(
353: getProperty(
354: am.getSrcAttrib())
355: .getName(), am
356: .getValue());
357: else
358: valueStr = am.getValue();
359:
360: if (valueStr != null)
361: setComponentAttributes(c, am
362: .getDestAttrib(), valueStr,
363: null);
364: }
365: }
366: }
367: } else {
368: XInstructionMapping instructionMapping = (XInstructionMapping) mapping;
369: //if ( tm.name.equals( "Instruction" )) {
370: if (layoutManager instanceof ColumnLayout)
371: ((ColumnLayout) layoutManager).addRowSpacer();
372: //}
373: }
374: } else {
375: Component c1 = (Component) componentFactory
376: .addComponent("Label", null, childName);
377: Component c2 = (Component) componentFactory
378: .addComponent("Edit", null, null);
379: }
380: } catch (Exception e) {
381: if (BuildProperties.DEBUG)
382: DebugLogger.logError("BUILDER",
383: "While adding the component element: "
384: + childName);
385: e.printStackTrace();
386: }
387: if (layoutManager instanceof ColumnLayout)
388: ((ColumnLayout) layoutManager).endRow();
389:
390: return null;
391: }
392:
393: /**
394: * Get a named property
395: * @param name the property name
396: * @return the property descriptor or null if no such named property is found
397: */
398: protected PropertyDescriptor getProperty(String name) {
399: int numProperties = properties.length;
400: for (int i = 0; i < numProperties; i++) {
401: if (properties[i].getName().equals(name))
402: return properties[i];
403: }
404:
405: return null;
406: }
407:
408: /**
409: * Get the page loader type - a unique name identifying the loader
410: * @return "ifnopath"
411: */
412: public String getType() {
413: return "pojo";
414: }
415: }
|