All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.zk.ui.sys.ComponentsCtrl Maven / Gradle / Ivy

There is a newer version: 10.0.0-jakarta
Show newest version
/* ComponentsCtrl.java

	Purpose:
		
	Description:
		
	History:
		Tue Aug  9 19:41:22     2005, Created by tomyeh

Copyright (C) 2005 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.sys;

import java.lang.reflect.Method;
import java.util.List;
import java.util.LinkedList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.io.StringWriter;
import java.net.URL;

import org.zkoss.lang.Classes;
import org.zkoss.lang.Objects;
import org.zkoss.lang.Library;
import org.zkoss.util.Pair;
import org.zkoss.util.FastReadCache;
import org.zkoss.util.Cache;
import org.zkoss.util.Maps;
import org.zkoss.json.JavaScriptValue;

import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.IdSpace;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Path;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.ComponentNotFoundException;
import org.zkoss.zk.ui.metainfo.LanguageDefinition;
import org.zkoss.zk.ui.metainfo.ComponentDefinition;
import org.zkoss.zk.ui.metainfo.ComponentInfo;
import org.zkoss.zk.ui.metainfo.AnnotationMap;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.xel.ExValue;

/**
 * Utilities for implementing components.
 *
 * @author tomyeh
 */
public class ComponentsCtrl {
	private static final ThreadLocal _compdef = new ThreadLocal();

	/** Returns the automatically generate component's UUID/ID.
	 */
	public static final String toAutoId(String prefix, int val) {
		return encodeId(new StringBuffer(16).append(prefix), val);
	}
	/** Returns an ID representing the specified number
	 * The ID consists of 0-9, a-z and _.
	 * @since 5.0.5
	 */
	public static final String encodeId(StringBuffer sb, int val) {
		//Thus, the number will 0, 1... max, 0, 1..., max, 0, 1 (less conflict)
		if (val < 0 && (val += Integer.MIN_VALUE) < 0)
			val = -val; //impossible but just in case

		do {
			//IE6/7's ID case insensitive (safer, though jQuery fixes it)
			int v = val % 37;
			val /= 37;
			if (v-- == 0) {
				sb.append('_');
			} else if (v < 10) {
				sb.append((char)('0' + v));
//			} else if (v < 36) {
			} else {
				sb.append((char)(v + ('a' - 10)));
//			} else {
//				sb.append((char)(v + ((int)'A' - 36)));
			}
		} while (val != 0);
		return sb.toString();
	}

	/** Returns whether an ID is generated automatically.
	 * Note: true is returned if id is null.
	 * Also notice that this method doesn't check if a custom ID generator
	 * ({@link org.zkoss.zk.ui.sys.IdGenerator}) is assigned.
	 * @since 5.0.3
	 */
	public static final boolean isAutoUuid(String id) {
		if (id == null)
			return true;

		//0: lower, 1: digit or upper, 2: letter or digit, 3: upper
		//See also DesktopImpl.updateUuidPrefix
		if (id.length() < 5)
			return false;
		char cc;
		return isLower(id.charAt(0))
			&& (isUpper(cc = id.charAt(1))  || isDigit(cc))
			&& (isUpper(cc = id.charAt(2)) || isLower(cc) || isDigit(cc))
			&& isUpper(id.charAt(3));
		
	}
	private static boolean isUpper(char cc) {
		return cc >= 'A' && cc <= 'Z';
	}
	private static boolean isLower(char cc) {
		return cc >= 'a' && cc <= 'z';
	}
	private static boolean isDigit(char cc) {
		return cc >= '0' && cc <= '9';
	}
	/** Checks if the given UUID is valid.
	 * UUID cannot be empty and can only have alphanumeric characters or underscore.
	 * @exception UiException if uuid is not valid.
	 */
	public static void checkUuid(String uuid) {
		int j;
		if (uuid == null || (j = uuid.length()) == 0)
			throw new UiException("uuid cannot be null or empty");

		while (--j >= 0) {
			final char cc = uuid.charAt(j);
			if ((cc < 'a' || cc > 'z') && (cc < 'A' || cc > 'Z')
			&& (cc < '0' || cc > '9') && cc != '_')
				throw new UiException("Illegal character, "+cc+", not allowed in uuid, "+uuid);
		}
	}

	/** Returns if the attribute name is reserved.
	 * If name is null, false is returned.
	 * @since 3.0.0
	 */
	public static final boolean isReservedAttribute(String name) {
		return name != null && !"use".equals(name) && !"if".equals(name)
			&& !"unless".equals(name) && !"apply".equals(name)
			&& !"forEach".equals(name);
	}
	/** Returns the current component info {@link ComponentInfo},
	 * definition ({@link ComponentDefinition} or null, which is used only by
	 * {@link org.zkoss.zk.ui.sys.UiEngine} to communicate with
	 * {@link org.zkoss.zk.ui.AbstractComponent}.
	 * @since 3.0.0
	 */
	public static final Object getCurrentInfo() {
		return _compdef.get();
	}
	/** Sets the current component definition, which is used only by
	 * {@link org.zkoss.zk.ui.sys.UiEngine} to communicate with
	 * {@link org.zkoss.zk.ui.AbstractComponent}.
	 * 

Used only internally. * @since 3.0.0 */ public static final void setCurrentInfo(ComponentDefinition compdef) { _compdef.set(compdef); } /** Sets the current component definition, which is used only by * {@link org.zkoss.zk.ui.sys.UiEngine} to communicate with * {@link org.zkoss.zk.ui.AbstractComponent}. *

Used only internally. * @since 3.0.0 */ public static void setCurrentInfo(ComponentInfo compInfo) { _compdef.set(compInfo); } /** Pares the event expression. * *

There are several formats for the event expression: *

    *
  • onClick
  • *
  • self.onClick
  • *
  • id.onClick
  • *
  • ../id1/id2.onClick
  • *
  • ${elexpr}.onClick
  • *
* * @param comp the component that the event expression is referenced to * @param evtexpr the event expression. * @param defaultComp the default component which is used when * evtexpr doesn't specify the component. * @param deferred whether to defer the conversion of the path * to a component. If true and EL not specified or evaluated to a string, * it returns the path directly rather than converting it to a component. * @return a two element array. The first element is the component * if deferred is false or EL is evaluated to a component, * or a path, otherwise. * The second component is the event name. * @since 3.0.0 */ public static Object[] parseEventExpression(Component comp, String evtexpr, Component defaultComp, boolean deferred) throws ComponentNotFoundException { final int j = evtexpr.lastIndexOf('.'); final String evtnm; Object target; if (j >= 0) { evtnm = evtexpr.substring(j + 1).trim(); String path = evtexpr.substring(0, j); if (path.length() > 0) { target = null; if (path.indexOf("${") >= 0) { final Object v = Executions.evaluate(comp, path, Object.class); if (v instanceof Component) { target = v; } else if (v == null) { throw new ComponentNotFoundException("EL evaluated to null: "+path); } else { path = Objects.toString(v); } } if (target == null) { path = path.trim(); if ("self".equals(path)) path = "."; target = deferred ? path: ".".equals(path) ? comp: Path.getComponent(comp.getSpaceOwner(), path); if (target == null && comp instanceof IdSpace && comp.getParent() != null) { target = Path.getComponent(comp.getParent().getSpaceOwner(), path); } } } else { target = defaultComp; } } else { evtnm = evtexpr.trim(); target = defaultComp; } if (!Events.isValid(evtnm)) throw new UiException("Not an event name: "+evtnm); return new Object[] {target, evtnm}; } /** * Applies the forward condition to the specified component. * *

The basic format:
* onEvent1=id1/id2.onEvent2,onEvent3=id3.onEvent4 * *

See {@link org.zkoss.zk.ui.metainfo.ComponentInfo#setForward} * for more information. * * @since 3.0.0 */ public static final void applyForward(Component comp, String forward) { if (forward == null) return; final Map> fwds = new LinkedHashMap>(); //remain the order Maps.parseMultiple(fwds, forward, ',', '\'', true, true); for (Map.Entry> me: fwds.entrySet()) { final String orgEvent = me.getKey(); if (orgEvent != null && !Events.isValid(orgEvent)) throw new UiException("Not an event name: "+orgEvent); final Collection conds = me.getValue(); for (String cond: conds) applyForward0(comp, orgEvent, cond); } } private static final void applyForward0(Component comp, String orgEvent, String cond) { int len; if (cond == null || (len = cond.length()) == 0) len = (cond = orgEvent).length(); //if condition not specified, assume same as orgEvent (to space owenr) Object data = null; for (int j = 0; j < len; ++j) { final char cc = cond.charAt(j); if (cc == '\\') ++j; //skip next else if (cc == '{') { for (int k = j + 1, depth = 0;; ++k) { if (k >= len) { j = k; break; } final char c2 = cond.charAt(k); if (c2 == '{') ++depth; else if (c2 == '}' && --depth < 0) { //found j = k; break; } } } else if (cc == '(') { //found final int k = cond.lastIndexOf(')'); if (k > j) { data = Executions.evaluate( comp, cond.substring(j + 1, k), Object.class); cond = cond.substring(0, j); break; } } } final Object[] result = parseEventExpression(comp, cond, null, true); final Object target = result[0]; if (target instanceof String) comp.addForward(orgEvent, (String)target, (String)result[1], data); else comp.addForward(orgEvent, (Component)target, (String)result[1], data); } /** Returns the method for handling the specified event, or null * if not available. */ public static final Method getEventMethod(Class cls, String evtnm) { final Pair, String> key = new Pair, String>(cls, evtnm); final Object o = _evtmtds.get(key); if (o != null) return o == Objects.UNKNOWN ? null: (Method)o; Method mtd = null; try { mtd = Classes.getCloseMethodBySubclass( cls, evtnm, new Class[] {Event.class}); //with event arg } catch (NoSuchMethodException ex) { try { mtd = cls.getMethod(evtnm); //no argument case } catch (NoSuchMethodException e2) { } } _evtmtds.put(key, mtd != null ? mtd: Objects.UNKNOWN); return mtd; } /** Sets the cache that stores the information about event handler methods. * *

Default: {@link FastReadCache}. In additions, the number of caches is default * to 97 and can be changed by use of the org.zkoss.zk.ui.eventMethods.cache.number * property. The maximal allowed size of each cache, if GC, is default to 30 * and can be changed by use of the org.zkoss.zk.ui.eventMethods.cache.maxSize * property. * * @param cache the cache. It cannot be null. It must be thread safe. * Once assigned, the caller shall not access it again. * @since 3.0.0 */ @SuppressWarnings("unchecked") public static final void setEventMethodCache(Cache cache) { if (cache == null) throw new IllegalArgumentException(); _evtmtds = cache; } /** A map of (Pair(Class,String evtnm), Method). */ private static Cache, String>, Object> _evtmtds = new FastReadCache, String>, Object> ( Library.getIntProperty("org.zkoss.zk.ui.event.methods.cache.maxSize", 600), 4*60*60*1000); /** An utilities to create an array of JavaScript objects * ({@link JavaScriptValue}) that can be used * to mount the specified widget at the clients. * * @since 5.0.0 */ public static final Collection redraw(Collection comps) { try { final StringWriter out = new StringWriter(1024*8); final List js = new LinkedList(); for (Component comp: comps) { ((ComponentCtrl)comp).redraw(out); final StringBuffer sb = out.getBuffer(); js.add(new JavaScriptValue(sb.toString())); sb.setLength(0); } return js; } catch (java.io.IOException ex) { throw new InternalError(); } } /** Represents a dummy definition. */ public static final ComponentDefinition DUMMY = new ComponentDefinition() { public LanguageDefinition getLanguageDefinition() { return null; } public String getName() { return "[anonymous]"; } public String getTextAs() { return null; } public boolean isChildAllowedInTextAs() { return false; } public boolean isMacro() { return false; } public String getMacroURI() { return null; } public boolean isInlineMacro() { return false; } public boolean isNative() { return false; } public boolean isBlankPreserved() { return false; } public Object getImplementationClass() { return Component.class; } public void setImplementationClass(Class cls) { throw new UnsupportedOperationException(); } public void setImplementationClass(String clsnm) { throw new UnsupportedOperationException(); } public Class resolveImplementationClass(Page page, String clsnm) throws ClassNotFoundException { return Component.class; } public boolean isInstance(org.zkoss.zk.ui.Component comp) { return comp != null; } public Component newInstance(Page page, String clsnm) { throw new UnsupportedOperationException(); } public Component newInstance(Class cls) { throw new UnsupportedOperationException(); } public void addMold(String name, String widgetClass) { throw new UnsupportedOperationException(); } public String getWidgetClass(Component comp, String moldName) { return null; } public String getDefaultWidgetClass(Component comp) { return null; } public void setDefaultWidgetClass(String widgetClass) { throw new UnsupportedOperationException(); } public boolean hasMold(String name) { return false; } public Collection getMoldNames() { return Collections.emptyList(); } public void addProperty(String name, String value) { throw new UnsupportedOperationException(); } public void applyProperties(Component comp) { } public void applyAttributes(Component comp) { } public Map evalProperties(Map propmap, Page owner, Component parent) { return propmap != null ? propmap: new HashMap(2); } public AnnotationMap getAnnotationMap() { return null; } public String getApply() { return null; } public ExValue[] getParsedApply() { return null; } public void setApply(String apply) { throw new UnsupportedOperationException(); } public URL getDeclarationURL() { return null; } public ComponentDefinition clone(LanguageDefinition langdef, String name) { throw new UnsupportedOperationException(); } public Object clone() { throw new UnsupportedOperationException(); } }; }