001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata masks may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: Theme.java,v $
031: * Revision 1.4 2005/10/11 18:52:08 colinmacleod
032: * Fixed some checkstyle and javadoc issues.
033: *
034: * Revision 1.3 2005/10/02 14:06:35 colinmacleod
035: * Added/improved log4j logging.
036: *
037: * Revision 1.2 2005/04/09 18:04:22 colinmacleod
038: * Changed copyright text to GPL v2 explicitly.
039: *
040: * Revision 1.1 2005/01/06 22:03:48 colinmacleod
041: * Moved up a version number.
042: * Changed copyright notices to 2005.
043: * Updated the documentation:
044: * - started working on multiproject:site docu.
045: * - changed the logo.
046: * Added checkstyle and fixed LOADS of style issues.
047: * Added separate thirdparty subproject.
048: * Added struts (in web), util and webgui (in webtheme) from ivata op.
049: *
050: * Revision 1.3 2004/07/13 19:48:11 colinmacleod
051: * Moved project to POJOs from EJBs.
052: * Applied PicoContainer to services layer (replacing session EJBs).
053: * Applied Hibernate to persistence layer (replacing entity EJBs).
054: *
055: * Revision 1.2 2004/03/21 21:16:38 colinmacleod
056: * Shortened name to ivata op.
057: *
058: * Revision 1.1.1.1 2004/01/27 20:59:52 colinmacleod
059: * Moved ivata op to SourceForge.
060: *
061: * Revision 1.2 2003/10/15 14:13:40 colin
062: * Fixes for XDoclet.
063: *
064: * Revision 1.1 2003/02/25 08:05:54 colin
065: * Moved to new subproject.
066: *
067: * Revision 1.3 2003/02/04 17:43:46 colin
068: * copyright notice
069: *
070: * Revision 1.2 2002/07/01 08:09:28 colin
071: * added new ANTLR parser
072: *
073: * Revision 1.1 2002/06/21 11:58:37 colin
074: * restructured com.ivata.mask.jsp into separate
075: * format, JavaScript, theme and tree.
076: *
077: * Revision 1.3 2002/05/10 12:44:12 colin
078: * Updated and extended JavaDoc
079: *
080: * Revision 1.2 2002/04/30 15:22:23 colin
081: * first functional version of the mail subproject in JBuilder
082: *
083: * Revision 1.1 2002/04/26 13:53:17 colin
084: * move to EJB/JBuilder project
085: *
086: * Revision 1.8 2002/01/30 18:34:58 colin
087: * made property/attribute names case sensitive
088: *
089: * Revision 1.7 2002/01/27 19:55:48 colin
090: * updated the themes by removing the multiple section tags and
091: * replacing them with one tag and a Properties instance in
092: * com.ivata.mask.web.theme.Theme
093: *
094: * Revision 1.6 2002/01/25 18:13:40 colin
095: * last version before changing the theme tags from individual member
096: * variables to references in a Properties collection
097: *
098: * Revision 1.5 2002/01/20 23:48:37 colin
099: * added toolbar
100: *
101: * Revision 1.4 2002/01/20 20:33:37 colin
102: * improved error handler for unsupported theme tags
103: *
104: * Revision 1.3 2002/01/20 19:28:25 colin
105: * added tab and tree tags
106: * implemented address book functionality
107: *
108: * Revision 1.2 2001/10/28 21:42:06 colin
109: * first working version of theme engine
110: *
111: * Revision 1.1 2001/10/28 16:45:42 colin
112: * initial theme controls the appearance of windows
113: * -----------------------------------------------------------------------------
114: */
115: package com.ivata.mask.web.theme;
116:
117: import org.apache.log4j.Logger;
118:
119: import java.util.Properties;
120:
121: import com.ivata.mask.util.CollectionHandling;
122: import com.ivata.mask.web.theme.parser.ThemeParser;
123:
124: /**
125: * <p>
126: * This class defines an interface to providing themes. In ivata op, themes are
127: * used for most HTML elements. The HTML itself is all located in one file,
128: * which uses tags to call this class and set the theme <i>sections </i>
129: * </p>
130: *
131: * <p>
132: * These sections usually correspond to either a whole tag or the start and end
133: * of a tag in the iiWebGUI tag library.
134: * </p>
135: *
136: * <p>
137: * This class stores all of these HTML sections. What's more, it provides
138: * parsing routines for dynamically applying attributes and properties within
139: * the HTML of any section. This in turn interacts with the code of iiWebGUI and
140: * the JSP files to customize the end result.
141: * </p>
142: *
143: * @since ivata masks 0.4 (2001-10-28)
144: * @author Colin MacLeod
145: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
146: * @version $Revision: 1.4 $
147: * @see com.ivata.mask.web.tag.theme
148: */
149: public class Theme extends Object {
150: /**
151: * Logger for this class.
152: */
153: private static final Logger logger = Logger.getLogger(Theme.class);
154:
155: /**
156: * <p>
157: * Stores all the default properties used by this theme.
158: * </p>
159: */
160: private Properties defaultProperties = new Properties();
161: /**
162: * <p>
163: * This field identifies the theme. You can have multiple themes within one
164: * WebApp to allow for multiple window styles, for example.
165: * </p>
166: *
167: * @see #Theme(String name)
168: */
169: private String name = null;
170: /**
171: * <p>
172: * Stores all the sections held by this Theme.
173: * </p>
174: */
175: private Properties sectionProperties = new Properties();
176:
177: /**
178: * <p>
179: * Creates new Theme with the name provided.
180: * </p>
181: *
182: * <p>
183: * You may create several themes within one WebApp to create multiple window
184: * styles, for example.
185: * </p>
186: *
187: * @param nameParam
188: * this name defines this theme
189: */
190: public Theme(final String nameParam) {
191: this .name = nameParam;
192: }
193:
194: /**
195: * <p>
196: * Check that a section is set. If the section has been defined, then return
197: * the value which was defined. Otherwise throw a ThemeUnsupportedException.
198: * </p>
199: *
200: * @param checkParam the name of the section to check
201: * @return the section text, if the section has been set. Otherwise null.
202: * @see #getSection(String name)
203: */
204: public final String checkSection(final String checkParam) {
205: if (logger.isDebugEnabled()) {
206: logger.debug("checkSection(String checkParam = "
207: + checkParam + ") - start");
208: }
209:
210: String returnString = getSection(checkParam);
211: if (returnString == null) {
212: throw new ThemeUnsupportedException(checkParam
213: + " has not been defined for the theme '" + name
214: + "'");
215: }
216:
217: if (logger.isDebugEnabled()) {
218: logger.debug("checkSection(String) - end - return value = "
219: + returnString);
220: }
221: return returnString;
222: }
223:
224: /**
225: * <p>
226: * Stores all the default properties used by this theme.
227: * </p>
228: *
229: *
230: * @return the current value of defaultProperties.
231: */
232: public final Properties getDefaultProperties() {
233: if (logger.isDebugEnabled()) {
234: logger.debug("getDefaultProperties() - start");
235: }
236:
237: if (logger.isDebugEnabled()) {
238: logger
239: .debug("getDefaultProperties() - end - return value = "
240: + defaultProperties);
241: }
242: return defaultProperties;
243: }
244:
245: /**
246: * <p>
247: * Get a single default property used in the theme. These properties are
248: * used when the theme text is parsed.
249: * </p>
250: *
251: * @param property the name of the property
252: * @return the default Value for the property
253: * @see #parse(String text, Properties properties)
254: * @see #parseSection(String name, Properties properties)
255: */
256: public final String getDefaultProperty(final String property) {
257: if (logger.isDebugEnabled()) {
258: logger.debug("getDefaultProperty(String property = "
259: + property + ") - start");
260: }
261:
262: String returnString = defaultProperties.getProperty(property);
263: if (logger.isDebugEnabled()) {
264: logger
265: .debug("getDefaultProperty(String) - end - return value = "
266: + returnString);
267: }
268: return returnString;
269: }
270:
271: /**
272: * <p>
273: * This field identifies the theme. You can have multiple themes within one
274: * WebApp to allow for multiple window styles, for example.
275: * </p>
276: *
277: * @return the current value of name.
278: * @see #Theme(String name)
279: */
280: public final String getName() {
281: if (logger.isDebugEnabled()) {
282: logger.debug("getName() - start");
283: }
284:
285: if (logger.isDebugEnabled()) {
286: logger.debug("getName() - end - return value = " + name);
287: }
288: return name;
289: }
290:
291: /**
292: * <p>
293: * Gets a section to be used in a WebGUI tag.
294: * </p>
295: *
296: * <p>
297: * <b>Note </b> that this method does not parse the section. For parsing,
298: * look at the <code>parseSection</code> method.
299: * </p>
300: *
301: * @param nameParam the name of the section you want to retrieve
302: * @return the section text matching the name if one exists, otherwise
303: * <code>null</code>.
304: * @see #parseSection
305: * @see #checkSection
306: */
307: public final String getSection(final String nameParam) {
308: if (logger.isDebugEnabled()) {
309: logger.debug("getSection(String nameParam = " + nameParam
310: + ") - start");
311: }
312:
313: String returnString = sectionProperties.getProperty(nameParam);
314: if (logger.isDebugEnabled()) {
315: logger.debug("getSection(String) - end - return value = "
316: + returnString);
317: }
318: return returnString;
319: }
320:
321: /**
322: * <p>
323: * Stores all the sections held by this Theme.
324: * </p>
325: *
326: * @return the current value of sectionProperties.
327: */
328: public final Properties getSectionProperties() {
329: if (logger.isDebugEnabled()) {
330: logger.debug("getSectionProperties() - start");
331: }
332:
333: if (logger.isDebugEnabled()) {
334: logger
335: .debug("getSectionProperties() - end - return value = "
336: + sectionProperties);
337: }
338: return sectionProperties;
339: }
340:
341: /**
342: * <p>
343: * Works like <code>parseSection</code> but operates on a section text you
344: * provide, rather than taking one of the sections defined in this class
345: * instance.
346: * </p>
347: *
348: * @param textParam the text of the section to parse for
349: * <code>PROPERTY</code> or <code>ATTRIBUTE</code> strings
350: * @param propertiesParam the properties to use when evaluating
351: * <code>PROPERTY</code> or <code>ATTRIBUTE</code> strings
352: * @return a parsed string where all instances of <code>PROPERTY</code> or
353: * <code>ATTRIBUTE</code> are replaced as appropriate, or an empty string
354: * if this section has not been defined.
355: * @see #parseSection
356: * @see #getSection
357: */
358: public final String parse(final String textParam,
359: final Properties propertiesParam) {
360: if (logger.isDebugEnabled()) {
361: logger.debug("parse(String textParam = " + textParam
362: + ", Properties propertiesParam = "
363: + propertiesParam + ") - start");
364: }
365:
366: if (textParam == null) {
367: if (logger.isDebugEnabled()) {
368: logger.debug("parse - end - return value = ");
369: }
370: return "";
371: }
372: // if a null properties was supplies, create a new, empty one
373: Properties properties;
374: if (propertiesParam == null) {
375: // just create an empty properties object
376: properties = new Properties();
377: } else {
378: properties = propertiesParam;
379: }
380: // mix in the default properties
381: properties = CollectionHandling.splice(properties,
382: defaultProperties);
383: try {
384: // let ANTLR take the strain...
385: String returnString = ThemeParser.parse(textParam,
386: properties, "text from '" + name + "'");
387: if (logger.isDebugEnabled()) {
388: logger.debug("parse - end - return value = "
389: + returnString);
390: }
391: return returnString;
392: } catch (Exception e) {
393: logger.error("parse(String, Properties)", e);
394:
395: throw new ThemeParseException(e);
396: }
397: }
398:
399: /**
400: * <p>
401: * Parse a string by replacing all the properties and attributes.
402: * </p>
403: *
404: * <p>
405: * This routine looks for Properties with the format
406: * <code>PROPERTY(name)</code>. These will be replaced with the value of
407: * the property provided from the supplied properties instance (
408: * <code>properties.get("name")</code>).
409: * <p>
410: *
411: * <p>
412: * The routine also identifies attributes, identified by the format string
413: * <code>ATTRIBUTE({name:}attribute{,default})</code>.
414: * </p>
415: *
416: * <p>
417: * In this case {name:} is an optional name (which defaults to the attribute
418: * name, if it is not supplied. This format string will be replaced by
419: * setting the attribute with the given name to
420: * <code>properties.get("name")</code>, if name was supplied, or
421: * <code>properties.get("attribute")</code>, if it wasn't.
422: * </p>
423: *
424: * <p>
425: * For an attribute, there is also the optional default parameter, which is
426: * separated from the attribute name by a comma. This value will be used,
427: * quoted, if the property was not set.
428: * </p>
429: *
430: * <h3>example</h3>
431: *
432: * <p>
433: * This is best illustrated by means of example. Consider the following
434: * theme section called "myFirstTag". This must have been set
435: * separately in the theme JSP file, using the
436: * {@link com.ivata.mask.web.tag.theme.SectionTag <theme:section>
437: * tag}.
438: * </p>
439: *
440: * <p>
441: * <code>
442: * <font color='blue'><table ATTRIBUTE(cellpadding)
443: * ATTRIBUTE(width:cellspacing) border=(border,0)><br/>
444: * <tr><br/>
445: * <td></font>PROPERTY(text)<font
446: * color='blue'></td><br/>
447: * </tr><br/>
448: * </table></font></code>
449: * </p>
450: *
451: * <p>
452: * Now look at the following Java code:
453: * </p>
454: *
455: * <p>
456: * <code>
457: * <font color='blue'>Properties</font> properties = <font
458: * color='red'>new</font> <font color='blue'>Properties</font>();<br/>
459: * properties.<font color='blue'>set</font>("width", 10);<br/>
460: * properties.<font color='blue'>set</font>("text", "This is an output
461: * test text.");<br/>
462: * <font color='blue'>String</font> sMyFirstTag = theme.<font
463: * color='blue'>parse</font>("myFirstTag", properties);</code>
464: * </p>
465: *
466: * <p>
467: * At this point, <code>sMyFirstTag</code> will contain:
468: * </p>
469: *
470: * <p>
471: * <code>
472: * <font color='blue'><table cellspacing='10' border='0'><br/>
473: * <tr><br/>
474: * <td></font>This is an output test
475: * text.<font color='blue'></td><br/>
476: * </tr><br/>
477: * </table></font></code>
478: * </p>
479: *
480: * @param nameParam the name of the section text to parse for
481: * <code>PROPERTY</code> or <code>ATTRIBUTE</code> strings
482: * @param propertiesParam the properties to use when evaluating
483: * <code>PROPERTY</code> or <code>ATTRIBUTE</code> strings
484: * @return a parsed string where all instances of <code>PROPERTY</code> or
485: * <code>ATTRIBUTE</code> are replaced as appropriate, or an empty
486: * string if this section has not been defined.
487: * @see #getSection(String name)
488: * @see #parse(String text, Properties properties)
489: */
490: public final String parseSection(final String nameParam,
491: final Properties propertiesParam) {
492: if (logger.isDebugEnabled()) {
493: logger.debug("parseSection(String nameParam = " + nameParam
494: + ", Properties propertiesParam = "
495: + propertiesParam + ") - start");
496: }
497:
498: String returnString = parse(checkSection(nameParam),
499: propertiesParam);
500: if (logger.isDebugEnabled()) {
501: logger.debug("parseSection - end - return value = "
502: + returnString);
503: }
504: return returnString;
505: }
506:
507: /**
508: * <p>
509: * Stores all the default properties used by this theme.
510: * </p>
511: *
512: * @param defaultPropertiesParam the new value of defaultProperties.
513: */
514: public final void setDefaultProperties(
515: final Properties defaultPropertiesParam) {
516: if (logger.isDebugEnabled()) {
517: logger
518: .debug("setDefaultProperties(Properties defaultPropertiesParam = "
519: + defaultPropertiesParam + ") - start");
520: }
521:
522: this .defaultProperties = defaultPropertiesParam;
523:
524: if (logger.isDebugEnabled()) {
525: logger.debug("setDefaultProperties(Properties) - end");
526: }
527: }
528:
529: /**
530: * <p>
531: * Set a single default property used in the theme. These properties are
532: * used when the theme text is parsed.
533: * </p>
534: *
535: * @param propertyParam the name of the property
536: * @param valueParam the default Value for the property
537: * @see #parse(String text, Properties properties)
538: * @see #parseSection(String name, Properties
539: * properties)
540: */
541: public final void setDefaultProperty(final String propertyParam,
542: final String valueParam) {
543: if (logger.isDebugEnabled()) {
544: logger.debug("setDefaultProperty(String propertyParam = "
545: + propertyParam + ", String valueParam = "
546: + valueParam + ") - start");
547: }
548:
549: defaultProperties.setProperty(propertyParam, valueParam);
550:
551: if (logger.isDebugEnabled()) {
552: logger.debug("setDefaultProperty(String, String) - end");
553: }
554: }
555:
556: /**
557: * <p>
558: * This field identifies the theme. You can have multiple themes within one
559: * WebApp to allow for multiple window styles, for example.
560: * </p>
561: *
562: * @see #Theme(String name)
563: * @param nameParam the new value of name.
564: */
565: public final void setName(final String nameParam) {
566: if (logger.isDebugEnabled()) {
567: logger.debug("setName(String nameParam = " + nameParam
568: + ") - start");
569: }
570:
571: this .name = nameParam;
572:
573: if (logger.isDebugEnabled()) {
574: logger.debug("setName(String) - end");
575: }
576: }
577:
578: /**
579: * <p>
580: * Set a section to be used in your theme.
581: * </p>
582: *
583: * <p>
584: * Called from the theme:section tag
585: * </p>
586: *
587: * <p>
588: * By convention, sections are named after the tags they are used in. If
589: * your empty tag is called tagName, the section for your tag should be
590: * called just that: "tagName".
591: * </p>
592: *
593: * <p>
594: * If, on the other hand, your tag has a body, then you will need two
595: * sections. Call the first "tagNameStart" (comes before the body) and
596: * "tagNameEnd" (comes after the body.
597: * </p>
598: *
599: * @param nameParam
600: * the name of the section you want to define. Overrides any
601: * previous section with the same name
602: * @param textParam
603: * the section text
604: */
605: public final void setSection(final String nameParam,
606: final String textParam) {
607: if (logger.isDebugEnabled()) {
608: logger
609: .debug("setSection(String nameParam = " + nameParam
610: + ", String textParam = " + textParam
611: + ") - start");
612: }
613:
614: this .sectionProperties.setProperty(nameParam, textParam);
615:
616: if (logger.isDebugEnabled()) {
617: logger.debug("setSection(String, String) - end");
618: }
619: }
620:
621: /**
622: * <p>
623: * Stores all the sections held by this Theme.
624: * </p>
625: *
626: * @param sectionPropertiesParam the new value of sectionProperties.
627: */
628: public final void setSectionProperties(
629: final Properties sectionPropertiesParam) {
630: if (logger.isDebugEnabled()) {
631: logger
632: .debug("setSectionProperties(Properties sectionPropertiesParam = "
633: + sectionPropertiesParam + ") - start");
634: }
635:
636: this .sectionProperties = sectionPropertiesParam;
637:
638: if (logger.isDebugEnabled()) {
639: logger.debug("setSectionProperties(Properties) - end");
640: }
641: }
642: }
|