001: /*
002: * Copyright (c) 1998-2004 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Sam
027: */
028:
029: package com.caucho.widget;
030:
031: import com.caucho.lifecycle.Lifecycle;
032: import com.caucho.util.L10N;
033:
034: import java.io.IOException;
035: import java.util.AbstractMap;
036: import java.util.Collections;
037: import java.util.HashSet;
038: import java.util.Map;
039: import java.util.Set;
040: import java.util.logging.Level;
041: import java.util.logging.Logger;
042:
043: /**
044: * A Widget stores request specific state (S).
045: */
046: abstract public class Widget<S extends WidgetState> extends
047: AbstractMap<String, Widget> {
048: private static L10N L = new L10N(Widget.class);
049:
050: static protected final Logger log = Logger.getLogger(Widget.class
051: .getName());
052:
053: private String _id;
054: private String _clientId;
055: private String _parameterName;
056: private String _modeParameterName;
057: private String _preferencePrefix;
058: private String _cssClass;
059: private WidgetPreferences _widgetPreferences;
060: private WidgetMode _widgetMode;
061: private HashSet<WidgetMode> _allowedWidgetModesSet;
062:
063: private Widget _parent;
064:
065: private Lifecycle _lifecycle = new Lifecycle();
066:
067: private RendererCache _rendererCache = new RendererCache();
068:
069: public Widget() {
070: }
071:
072: public Widget(String id) {
073: setId(id);
074: }
075:
076: public Widget(Widget parent) {
077: setParent(parent);
078: parent.put(null, this );
079: }
080:
081: public Widget(Widget parent, String id) {
082: setParent(parent);
083: setId(id);
084: parent.put(getId(), this );
085: }
086:
087: public void setParent(Widget parent) {
088: _parent = parent;
089: }
090:
091: public Widget getParent() {
092: return _parent;
093: }
094:
095: public void setId(String id) {
096: _id = id;
097: }
098:
099: public String getId() {
100: return _id;
101: }
102:
103: protected String calculateId(Widget child) {
104: return "x" + size();
105: }
106:
107: /**
108: * Default is a concatentation of all parent id's separated
109: * by `_'.
110: */
111: public void setClientId(String clientId) {
112: _clientId = clientId;
113: }
114:
115: public String getClientId() {
116: if (_clientId == null) {
117: if (_parent == null)
118: _clientId = getId();
119: else {
120: StringBuffer buf = new StringBuffer();
121:
122: appendId(buf, _parent, '_');
123: buf.append(getId());
124:
125: _clientId = buf.toString();
126: }
127: }
128:
129: return _clientId;
130: }
131:
132: private static void appendId(StringBuffer buf, Widget w, char sep) {
133: if (w == null)
134: return;
135:
136: if (w._parent != null) {
137: appendId(buf, w._parent, sep);
138: }
139:
140: if (w.getId() != null) {
141: buf.append(w.getId());
142: buf.append(sep);
143: }
144: }
145:
146: /**
147: * Default is a concatentation of all parent id's separated
148: * by `.'.
149: */
150: public void setParameterName(String parameterName) {
151: _parameterName = parameterName;
152: }
153:
154: public String getParameterName() {
155: if (_parameterName == null) {
156: if (_parent == null)
157: _parameterName = getId();
158: else {
159: StringBuffer buf = new StringBuffer();
160:
161: appendId(buf, _parent, '.');
162: buf.append(getId());
163:
164: _parameterName = buf.toString();
165: }
166: }
167:
168: return _parameterName;
169: }
170:
171: /**
172: * Set a css class for renders that use it, the default is
173: * to use the value of "<code>getId()</code> <code>shortClassName</code>"
174: * where <i>shortClassName</i> is the classname portion (no package) of the
175: * widget's class.
176: *
177: * If the passed string starts with "+", for example "+clear",
178: * then the string is appended to the current cssClass.
179: *
180: * If the passed string starts with "-", for example "-clear",
181: * then the string is removed from the current cssClass.
182: *
183: * For example, a TextWidget with id "phoneNumber" and clientId
184: * "form.phoneNumber" will have a default cssClass of
185: * "textWidget * phoneNumber".
186: */
187: public void setCssClass(String cssClass) {
188: boolean add = cssClass.charAt(0) == '+';
189: boolean del = cssClass.charAt(0) == '-';
190:
191: if (add || del) {
192: cssClass = cssClass.substring(1);
193: String current = getCssClass();
194:
195: StringBuffer cssClassBuf = new StringBuffer();
196:
197: if (current != null && current.length() > 0) {
198: String[] split = current.split("\\s*");
199:
200: for (int i = 0; i < split.length; i++) {
201: String token = split[i];
202:
203: if (token.equals(cssClass))
204: continue;
205:
206: cssClassBuf.append(token);
207: }
208: }
209:
210: if (add)
211: cssClassBuf.append(cssClass);
212:
213: _cssClass = cssClassBuf.toString();
214: } else {
215: _cssClass = cssClass;
216: }
217: }
218:
219: /**
220: * Used for information purposes
221: */
222: public String getLogId() {
223: StringBuffer buf = new StringBuffer();
224:
225: String classname = getClass().getName();
226: classname = classname.substring(classname.lastIndexOf('.') + 1);
227: buf.append(classname);
228: buf.append('[');
229:
230: appendLogId(buf, this );
231:
232: if (_parent == null)
233: buf.append('/');
234:
235: buf.append(']');
236:
237: return buf.toString();
238: }
239:
240: private static void appendLogId(StringBuffer buf, Widget w) {
241: if (w._parent != null) {
242: appendLogId(buf, w._parent);
243: buf.append('/');
244: }
245:
246: if (w.getId() != null)
247: buf.append(w.getId());
248: }
249:
250: /**
251: * Used by implementing classes to get a css class
252: * appropriate for this widget.
253: */
254: public String getCssClass() {
255: if (_cssClass == null) {
256: StringBuffer buf = new StringBuffer();
257:
258: String shortName = getClass().getName();
259:
260: int i = shortName.lastIndexOf('.') + 1;
261:
262: for (; i < shortName.length(); i++)
263: buf.append(shortName.charAt(i));
264:
265: buf.append(' ');
266: buf.append(getId());
267:
268: _cssClass = buf.toString();
269: }
270:
271: return _cssClass;
272: }
273:
274: /**
275: * Default is to the parameterName with a prefix of "_" and a suffix
276: * of "_".
277: */
278: public void setModeParameterName(String modeParameterName) {
279: _modeParameterName = modeParameterName;
280: }
281:
282: public String getModeParameterName() {
283: if (_modeParameterName == null)
284: _modeParameterName = "_" + getParameterName() + "_";
285:
286: return _modeParameterName;
287: }
288:
289: public WidgetPreferences createWidgetPreferences() {
290: if (_widgetPreferences == null) {
291: _widgetPreferences = new WidgetPreferences();
292: }
293:
294: return _widgetPreferences;
295: }
296:
297: public void addPreference(WidgetPreference widgetPreference) {
298: WidgetPreferences widgetPreferences = createWidgetPreferences();
299:
300: widgetPreferences.addPreference(widgetPreference);
301: }
302:
303: /**
304: * If a preference "pref" is not found as a preference
305: * specifically set for this widget, the prefix is prepended
306: * and the connection preferences are checked; the default is "<i>id.</i>",
307: * for example connection.getPreferenceValue("<i>id.</i>pref").
308: */
309: public void setPreferencePrefix(String preferencePrefix) {
310: _preferencePrefix = preferencePrefix;
311: }
312:
313: /**
314: * Return a preference value.
315: */
316: public String getPreferenceValue(WidgetConnection connection,
317: String key) {
318: String[] values = getPreferenceValues(connection, key);
319:
320: if (values == null)
321: return null;
322: else if (values.length == 0)
323: return "";
324: else
325: return values[0];
326: }
327:
328: /**
329: * Return preference values.
330: */
331: public String[] getPreferenceValues(WidgetConnection connection,
332: String key) {
333: WidgetPreference widgetPreference = getWidgetPreference(key);
334:
335: boolean isReadOnly = widgetPreference != null
336: && !widgetPreference.isReadOnly();
337:
338: String[] values = null;
339:
340: if (widgetPreference != null)
341: values = widgetPreference.getValues();
342:
343: if (!isReadOnly) {
344: key = key + _preferencePrefix;
345: values = connection.getPreferenceValues(key, values);
346: }
347:
348: return values;
349: }
350:
351: protected WidgetPreference getWidgetPreference(String key) {
352: if (_widgetPreferences == null)
353: return null;
354: else
355: return _widgetPreferences.get(key);
356:
357: }
358:
359: /**
360: * The default mode to use unless it specified in the State,
361: * default is WidgetMode.VIEW.
362: */
363: public void setWidgetMode(WidgetMode widgetMode) {
364: _widgetMode = widgetMode;
365: }
366:
367: WidgetMode getWidgetMode() {
368: return _widgetMode;
369: }
370:
371: /**
372: * Default is to allow all widget modes.
373: */
374: public void addAllowedWidgetMode(WidgetMode widgetMode) {
375: if (_allowedWidgetModesSet == null)
376: _allowedWidgetModesSet = new HashSet<WidgetMode>();
377:
378: _allowedWidgetModesSet.add(widgetMode);
379: }
380:
381: public boolean isWidgetModeAllowed(WidgetMode widgetMode) {
382: if (_allowedWidgetModesSet == null)
383: return true;
384: else
385: return _allowedWidgetModesSet.contains(widgetMode);
386: }
387:
388: public void init() throws WidgetException {
389: }
390:
391: private void initAll() throws WidgetException {
392: if (!_lifecycle.toInitializing())
393: return;
394:
395: if (_id == null && _parent != null)
396: throw new IllegalStateException(L.l("`{0}' is required",
397: "id"));
398:
399: if (_preferencePrefix == null)
400: _preferencePrefix = _id + ".";
401:
402: init();
403:
404: _lifecycle.toActive();
405: }
406:
407: public Set<Map.Entry<String, Widget>> entrySet() {
408: return (Set<Map.Entry<String, Widget>>) Collections.EMPTY_SET;
409: }
410:
411: public Widget put(String id, Widget value) {
412: throw new UnsupportedOperationException();
413: }
414:
415: /**
416: * Called once for each request to restore the state of the widget
417: * for the request.
418: */
419: protected S decode(WidgetConnection connection)
420: throws WidgetException {
421: if (log.isLoggable(Level.FINER))
422: log.finer(L.l("decode {0}", getLogId()));
423:
424: S state = createState(connection);
425: state.setWidget(this );
426:
427: String parameterName = getParameterName();
428:
429: if (parameterName == null && getParent() != null)
430: throw new IllegalStateException(L.l("`{0}' cannot be null",
431: "parameter-name"));
432:
433: if (parameterName != null) {
434: String[] data = connection
435: .getParameterValues(getParameterName());
436:
437: if (log.isLoggable(Level.FINEST))
438: log.finest(L.l("data from parameter {0} is {1}",
439: getParameterName(), data));
440:
441: state.decode(data);
442: }
443:
444: decodeChildren(connection, state);
445:
446: return state;
447: }
448:
449: protected void decodeChildren(WidgetConnection connection,
450: WidgetState state) throws WidgetException {
451: if (!isEmpty()) {
452: for (Widget child : values()) {
453: decodeChild(connection, state, child);
454: }
455: }
456: }
457:
458: protected WidgetState decodeChild(WidgetConnection connection,
459: WidgetState this State, Widget child) throws WidgetException {
460: WidgetState childState = child.decode(connection);
461: childState.setParent(this State);
462:
463: this State.put(child.getId(), childState);
464:
465: return childState;
466: }
467:
468: abstract protected S createState(WidgetConnection connection)
469: throws WidgetException;
470:
471: private void encodeTree(WidgetURL url, WidgetState state,
472: Widget target) throws WidgetException {
473: encodeTree(url, state, target, false, false);
474: }
475:
476: private void encodeTree(WidgetURL url, WidgetState state,
477: Widget target, boolean isAction, boolean sawTarget)
478: throws WidgetException {
479: if (target == this ) {
480: sawTarget = true;
481: isAction = isAction(state);
482: }
483:
484: encode(url, state, isAction);
485:
486: if (!isEmpty()) {
487: for (Widget child : values()) {
488: WidgetState childState = state.get(child.getId());
489:
490: if (childState == null) {
491: throw new IllegalStateException(L.l(
492: "no state for `{0}', missing decode()?",
493: child.getLogId()));
494: }
495:
496: child.encodeTree(url, childState, target, isAction,
497: sawTarget);
498: }
499: }
500: }
501:
502: protected void encode(WidgetURL url, WidgetState state,
503: boolean isAction) throws WidgetException {
504: if (log.isLoggable(Level.FINEST))
505: log.finest(L.l("encode {0}", getLogId()));
506:
507: WidgetMode widgetMode = state._widgetMode;
508:
509: if (widgetMode != null) {
510: if (widgetMode == WidgetMode.VIEW
511: || widgetMode.equals(WidgetMode.VIEW)) {
512: if (state.getParent() != null)
513: url.setParameter(getModeParameterName(), widgetMode
514: .toString());
515: } else {
516: url.setParameter(getModeParameterName(), widgetMode
517: .toString());
518: }
519: }
520:
521: if (isAction && isActionParameter(state)) {
522: url.setAction(true);
523: } else {
524: String[] data = state.encode();
525:
526: if (data != null)
527: url.setParameter(getParameterName(), data);
528: }
529: }
530:
531: /**
532: * Does this widget submit a value directly, s
533: * in some manner other than a parameter to the url?
534: *
535: * This value only has an effect if a url is being made
536: * from a widget that has a true value
537: * for isAction().
538: *
539: * For example, HTML fields like <input> return true.
540: */
541: protected boolean isActionParameter(WidgetState state) {
542: return false;
543: }
544:
545: /**
546: * Does this widget make it so that children with isActionParameter()
547: * can submit values without encoding them?
548: *
549: * For example, an HTML <form> does this.
550: */
551: protected boolean isAction(WidgetState state) {
552: return false;
553: }
554:
555: /**
556: * Derived classes use this to create a url.
557: *
558: * The widget tree is followed parent by parent until the top is reached,
559: * and then the connection is used to create a url for that widget.
560: */
561: protected WidgetURL createURL(WidgetConnection connection)
562: throws WidgetException {
563: Widget target = this ;
564: Widget top = this ;
565:
566: while (top.getParent() != null)
567: top = top.getParent();
568:
569: WidgetURL url = connection.createURL();
570: WidgetState state = connection.prepare(top);
571:
572: top.encodeTree(url, state, target);
573:
574: return url;
575: }
576:
577: public void render(WidgetConnection connection, S widgetState)
578: throws WidgetException, IOException {
579: if (!_lifecycle.isActive())
580: initAll();
581:
582: WidgetRenderer<S> widgetRenderer = _rendererCache.getRenderer(
583: connection, this , widgetState);
584:
585: widgetRenderer.render(connection, widgetState);
586: }
587:
588: public void destroy() {
589: if (!_lifecycle.toDestroying())
590: return;
591:
592: _lifecycle.toDestroy();
593: }
594: }
|