001: /* ComponentsCtrl.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Aug 9 19:41:22 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zk.ui.sys;
020:
021: import java.lang.reflect.Method;
022: import java.util.Iterator;
023: import java.util.Collection;
024: import java.util.Collections;
025: import java.util.Map;
026: import java.util.HashMap;
027: import java.util.LinkedHashMap;
028: import java.util.Date;
029: import java.net.URL;
030:
031: import org.zkoss.lang.Strings;
032: import org.zkoss.lang.Classes;
033: import org.zkoss.lang.Objects;
034: import org.zkoss.util.Pair;
035: import org.zkoss.util.ThreadLocalCache;
036: import org.zkoss.util.Cache;
037: import org.zkoss.util.Maps;
038:
039: import org.zkoss.zk.ui.Executions;
040: import org.zkoss.zk.ui.Page;
041: import org.zkoss.zk.ui.Component;
042: import org.zkoss.zk.ui.Path;
043: import org.zkoss.zk.ui.UiException;
044: import org.zkoss.zk.ui.WrongValueException;
045: import org.zkoss.zk.ui.ComponentNotFoundException;
046: import org.zkoss.zk.ui.metainfo.LanguageDefinition;
047: import org.zkoss.zk.ui.metainfo.ComponentDefinition;
048: import org.zkoss.zk.ui.metainfo.ComponentInfo;
049: import org.zkoss.zk.ui.metainfo.AnnotationMap;
050: import org.zkoss.zk.ui.event.Event;
051: import org.zkoss.zk.ui.event.Events;
052:
053: /**
054: * Utilities for implementing components.
055: *
056: * @author tomyeh
057: */
058: public class ComponentsCtrl {
059: /** The prefix for auto generated ID. */
060: private static final String AUTO_ID_PREFIX = "z_";
061: /** The anonymous UUID. Used only internally.
062: */
063: public static final String ANONYMOUS_ID = "z__i";
064:
065: private static final ThreadLocal _compdef = new ThreadLocal();
066:
067: /** Returns the automatically generate component's UUID/ID.
068: */
069: public static final String toAutoId(String prefix, int id) {
070: final StringBuffer sb = new StringBuffer(16).append(
071: AUTO_ID_PREFIX).append(prefix).append('_');
072: Strings.encode(sb, id);
073: return sb.toString();
074: }
075:
076: /**
077: * @deprecated As of release 2.4.1, replaced by {@link #ANONYMOUS_ID}
078: */
079: public static final String getAnonymousId() {
080: return ANONYMOUS_ID;
081: }
082:
083: /** Returns whether an ID is generated automatically.
084: */
085: public static final boolean isAutoId(String id) {
086: return id.startsWith(AUTO_ID_PREFIX)
087: && id.indexOf('_', AUTO_ID_PREFIX.length()) > 0;
088: }
089:
090: /** Returns whether an ID is a valid UUID. */
091: public static final boolean isUuid(String id) {
092: return isAutoId(id);
093: }
094:
095: /** Returns if the attribute name is reserved.
096: * If name is null, false is returned.
097: * @since 3.0.0
098: */
099: public static final boolean isReservedAttribute(String name) {
100: return name != null && !"use".equals(name)
101: && !"if".equals(name) && !"unless".equals(name)
102: && !"apply".equals(name) && !"forEach".equals(name);
103: }
104:
105: /** Returns the current component info {@link ComponentInfo},
106: * definition ({@link ComponentDefinition} or null, which is used only by
107: * {@link org.zkoss.zk.ui.sys.UiEngine} to communicate with
108: * {@link org.zkoss.zk.ui.AbstractComponent}.
109: * @since 3.0.0
110: */
111: public static final Object getCurrentInfo() {
112: return _compdef.get();
113: }
114:
115: /** Sets the current component definition, which is used only by
116: * {@link org.zkoss.zk.ui.sys.UiEngine} to communicate with
117: * {@link org.zkoss.zk.ui.AbstractComponent}.
118: * <p>Used only internally.
119: * @since 3.0.0
120: */
121: public static final void setCurrentInfo(ComponentDefinition compdef) {
122: _compdef.set(compdef);
123: }
124:
125: /** Sets the current component definition, which is used only by
126: * {@link org.zkoss.zk.ui.sys.UiEngine} to communicate with
127: * {@link org.zkoss.zk.ui.AbstractComponent}.
128: * <p>Used only internally.
129: * @since 3.0.0
130: */
131: public static void setCurrentInfo(ComponentInfo compInfo) {
132: _compdef.set(compInfo);
133: }
134:
135: /** Pares the event expression.
136: *
137: * <p>There are several formats for the event expression:
138: * <ul>
139: * <li>onClick</li>
140: * <li>self.onClick</li>
141: * <li>id.onClick</li>
142: * <li>../id1/id2.onClick</li>
143: * <li>${elexpr}.onClick</li>
144: * </ul>
145: *
146: * @param comp the component that the event expression is referenced to
147: * @param evtexpr the event expression.
148: * @param defaultComp the default component which is used when
149: * evtexpr doesn't specify the component.
150: * @param deferred whether to defer the conversion of the path
151: * to a component. If true and EL not specified or evaluated to a string,
152: * it returns the path directly rather than converting it to a component.
153: * @return a two element array. The first element is the component
154: * if deferred is false or EL is evaluated to a component,
155: * or a path, otherwise.
156: * The second component is the event name.
157: * @since 3.0.0
158: */
159: public static Object[] parseEventExpression(Component comp,
160: String evtexpr, Component defaultComp, boolean deferred)
161: throws ComponentNotFoundException {
162: final int j = evtexpr.lastIndexOf('.');
163: final String evtnm;
164: Object target;
165: if (j >= 0) {
166: evtnm = evtexpr.substring(j + 1).trim();
167: String path = evtexpr.substring(0, j);
168: if (path.length() > 0) {
169: target = null;
170: if (path.indexOf("${") >= 0) {
171: final Object v = Executions.evaluate(comp, path,
172: Object.class);
173: if (v instanceof Component) {
174: target = (Component) v;
175: } else if (v == null) {
176: throw new ComponentNotFoundException(
177: "EL evaluated to null: " + path);
178: } else {
179: path = Objects.toString(v);
180: }
181: }
182:
183: if (target == null) {
184: path = path.trim();
185: if ("self".equals(path))
186: path = ".";
187:
188: target = deferred ? (Object) path : "."
189: .equals(path) ? comp : Path.getComponent(
190: comp.getSpaceOwner(), path);
191: }
192: } else {
193: target = defaultComp;
194: }
195: } else {
196: evtnm = evtexpr.trim();
197: target = defaultComp;
198: }
199:
200: if (!Events.isValid(evtnm))
201: throw new UiException("Not an event name: " + evtnm);
202: return new Object[] { target, evtnm };
203: }
204:
205: /**
206: * Applies the forward condition to the specified component.
207: *
208: * <p>The basic format:<br/>
209: * <code>onEvent1=id1/id2.onEvent2,onEvent3=id3.onEvent4</code>
210: *
211: * <p>See {@link org.zkoss.zk.ui.metainfo.ComponentInfo#setForward}
212: * for more information.
213: *
214: * @since 3.0.0
215: */
216: public static final void applyForward(Component comp, String forward) {
217: if (forward == null)
218: return;
219:
220: final Map fwds = new LinkedHashMap(); //remain the order
221: Maps.parse(fwds, forward, ',', '\'', true);
222:
223: for (Iterator it = fwds.entrySet().iterator(); it.hasNext();) {
224: final Map.Entry me = (Map.Entry) it.next();
225: final String orgEvent = (String) me.getKey();
226: if (orgEvent != null && !Events.isValid(orgEvent))
227: throw new UiException("Not an event name: " + orgEvent);
228:
229: final Object[] result = parseEventExpression(comp,
230: (String) me.getValue(), null, true);
231:
232: final Object target = result[0];
233: if (target instanceof String)
234: comp.addForward(orgEvent, (String) target,
235: (String) result[1]);
236: else
237: comp.addForward(orgEvent, (Component) target,
238: (String) result[1]);
239: }
240: }
241:
242: /** Parses a script by resolving #{xx} to make it executable
243: * at the client.
244: *
245: * @param comp the component used to resolve the EL expression.
246: * @param script the Java script to convert
247: * @since 2.4.0
248: */
249: public static String parseClientScript(Component comp, String script) {
250: StringBuffer sb = null;
251: for (int j = 0, len = script.length();;) {
252: final int k = script.indexOf("#{", j);
253: if (k < 0)
254: return sb != null ? sb.append(script.substring(j))
255: .toString() : script;
256:
257: final int l = script.indexOf('}', k + 2);
258: if (l < 0)
259: throw new WrongValueException(
260: "Illegal script: unclosed EL expression.\n"
261: + script);
262:
263: if (sb == null)
264: sb = new StringBuffer(len);
265: sb.append(script.substring(j, k));
266:
267: //eval EL
268: Object val = Executions.evaluate(comp, '$' + script
269: .substring(k + 1, l + 1), Object.class);
270: if (val == null || (val instanceof Number)) {
271: sb.append(val);
272: } else if (val instanceof Component) {
273: sb.append(" $e('").append(
274: Strings.escape(((Component) val).getUuid(),
275: "'\\")).append("')");
276: } else if (val instanceof Date) {
277: sb.append(" new Date(").append(((Date) val).getTime())
278: .append(')');
279: } else { //FUTURE: regex
280: sb.append('\'').append(
281: Strings.escape(val.toString(), "'\\")).append(
282: '\'');
283: }
284:
285: //next
286: j = l + 1;
287: }
288: }
289:
290: /** Returns the method for handling the specified event, or null
291: * if not available.
292: */
293: public static final Method getEventMethod(Class cls, String evtnm) {
294: final Pair key = new Pair(cls, evtnm);
295: final Object o = _evtmtds.get(key);
296: if (o != null)
297: return o == Objects.UNKNOWN ? null : (Method) o;
298:
299: Method mtd = null;
300: try {
301: mtd = Classes.getCloseMethodBySubclass(cls, evtnm,
302: new Class[] { Event.class }); //with event arg
303: } catch (NoSuchMethodException ex) {
304: try {
305: mtd = cls.getMethod(evtnm, null); //no argument case
306: } catch (NoSuchMethodException e2) {
307: }
308: }
309: _evtmtds.put(key, mtd != null ? mtd : Objects.UNKNOWN);
310: return mtd;
311: }
312:
313: /** Sets the cache that stores the information about event handler methods.
314: *
315: * <p>Since the performance of the cache is critical to the
316: * performance of the overall system. There is several options to
317: * choose from:
318: *
319: * <ol>
320: * <li>{@link ThreadLocalCache}: the default.
321: * It is the fastest but consumes more memory since it maintains
322: * a cache per thread (about 10MB - 16M for over 400 concurrent users).
323: * <li>{@link org.zkoss.util.MultiCache}. It is the slowest but
324: * consumes less memory.</li>
325: * </ol>
326: *
327: * @param cache the cache. It cannot be null. It must be thread safe.
328: * Once assigned, the caller shall not access it again.
329: * @since 3.0.0
330: */
331: public static final void setEventMethodCache(Cache cache) {
332: if (cache == null)
333: throw new IllegalArgumentException();
334: _evtmtds = cache;
335: }
336:
337: /** A map of (Pair(Class,String evtnm), Method). */
338: private static Cache _evtmtds = new ThreadLocalCache();
339:
340: /** Represents a dummy definition. */
341: public static final ComponentDefinition DUMMY = new ComponentDefinition() {
342: public LanguageDefinition getLanguageDefinition() {
343: return null;
344: }
345:
346: public String getName() {
347: return "[anonymous]";
348: }
349:
350: public String getTextAs() {
351: return null;
352: }
353:
354: public boolean isMacro() {
355: return false;
356: }
357:
358: public String getMacroURI() {
359: return null;
360: }
361:
362: public boolean isInlineMacro() {
363: return false;
364: }
365:
366: public boolean isNative() {
367: return false;
368: }
369:
370: public Object getImplementationClass() {
371: return Component.class;
372: }
373:
374: public void setImplementationClass(Class cls) {
375: throw new UnsupportedOperationException();
376: }
377:
378: public void setImplementationClass(String clsnm) {
379: throw new UnsupportedOperationException();
380: }
381:
382: public Class resolveImplementationClass(Page page, String clsnm)
383: throws ClassNotFoundException {
384: return Component.class;
385: }
386:
387: public boolean isInstance(org.zkoss.zk.ui.Component comp) {
388: return comp != null;
389: }
390:
391: public Component newInstance(Page page, String clsnm) {
392: throw new UnsupportedOperationException();
393: }
394:
395: public Component newInstance(Class cls) {
396: throw new UnsupportedOperationException();
397: }
398:
399: public void addMold(String name, String moldURI) {
400: throw new UnsupportedOperationException();
401: }
402:
403: public void addMold(String name,
404: org.zkoss.zk.ui.render.ComponentRenderer renderer) {
405: throw new UnsupportedOperationException();
406: }
407:
408: public Object getMoldURI(Component comp, String name) {
409: return null;
410: }
411:
412: public boolean hasMold(String name) {
413: return false;
414: }
415:
416: public Collection getMoldNames() {
417: return Collections.EMPTY_LIST;
418: }
419:
420: public void addProperty(String name, String value) {
421: throw new UnsupportedOperationException();
422: }
423:
424: public void applyProperties(Component comp) {
425: }
426:
427: public Map evalProperties(Map propmap, Page owner,
428: Component parent) {
429: return propmap != null ? propmap : new HashMap(3);
430: }
431:
432: public AnnotationMap getAnnotationMap() {
433: return null;
434: }
435:
436: public URL getDeclarationURL() {
437: return null;
438: }
439:
440: public ComponentDefinition clone(LanguageDefinition langdef,
441: String name) {
442: throw new UnsupportedOperationException();
443: }
444:
445: public Object clone() {
446: throw new UnsupportedOperationException();
447: }
448: };
449: }
|