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

org.zkoss.zk.ui.metainfo.Property Maven / Gradle / Ivy

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

	Purpose:
		
	Description:
		
	History:
		Sun Apr 16 14:55:35     2006, Created by tomyeh

Copyright (C) 2006 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.metainfo;

import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Exceptions;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.ext.DynamicPropertied;
import org.zkoss.zk.ui.ext.Native;
import org.zkoss.zk.ui.sys.ExecutionCtrl;
import org.zkoss.zk.ui.sys.WebAppCtrl;
import org.zkoss.zk.ui.util.ConditionImpl;
import org.zkoss.zk.xel.EvaluatorRef;
import org.zkoss.zk.xel.ExValue;

/**
 * Information about how to initialize a property (a.k.a., a field of a component).
 * There are two kind of properties: one is a String instance (either
 * a string value or an expression), and the other is
 * a {@link NativeInfo} instance. The later is also called the native content.
 *
 * @author tomyeh
 */
public class Property extends ConditionValue {
	private static final Logger log = LoggerFactory.getLogger(Property.class);
	private static final long serialVersionUID = 20060622L;

	private final String _name;
	/** The value if it is not the native content.
	 * Exactly one of _value and _navval is non-null.
	 */
	private final ExValue _value;
	/** The value if it is the native content.
	 * Exactly one of _value and _navval is non-null.
	 */
	private final NativeInfo _navval;
	/** Used to optimize {@link #resolve}. */
	private transient volatile Class _lastcls;
	/** The method, or null if more than two methods are found
	 * (and use {@link #_mtds} in this case).
	 */
	private transient Method _mtd;
	/** Used more than two methods are found, or null if only one method
	 * (and use {@link #_mtd} in this case).
	 */
	private transient Method[] _mtds;

	/** Constructs a property with a class that is known in advance.
	 * @exception IllegalArgumentException if name is null
	 */
	public Property(EvaluatorRef evalr, String name, String value,
	ConditionImpl cond) {
		this(evalr, name, value, null, cond);
	}
	/** Constructs a property with the native content.
	 * The native content is represented by {@link NativeInfo},
	 * i.e., a XML fragment (a.k.a., a tree of {@link ComponentInfo}.
	 * @since 3.5.0
	 */
	public Property(EvaluatorRef evalr, String name, NativeInfo value,
	ConditionImpl cond) {
		this(evalr, name, null, value, cond);
	}
	private Property(EvaluatorRef evalr, String name, String value,
	NativeInfo navval, ConditionImpl cond) {
		super(evalr, cond);

		if (name == null)
			throw new IllegalArgumentException("null");

		_name = name;
		_navval = navval;
		_value = navval != null ? null: new ExValue(value, Object.class);
			//type will be fixed when mapped to a method
	}

	/** Returns the name of the property.
	 */
	public String getName() {
		return _name;
	}
	/** Returns the raw value of the property.
	 * Note: it is the original value without evaluation.
	 * In other words, it may contain EL expressions.
	 * @exception UnsupportedOperationException if value is the native content,
	 * i.e., it is constructed by use of {@link #Property(EvaluatorRef, String, NativeInfo, ConditionImpl)}.
	 * @since 3.0.0
	 */
	public String getRawValue() {
		if (_value == null)
			throw new UnsupportedOperationException("native content");
		return _value.getRawValue();
	}
	/** Sets the raw value of the property.
	 * @exception UnsupportedOperationException if value is the native content,
	 * i.e., it is constructed by use of {@link #Property(EvaluatorRef, String, NativeInfo, ConditionImpl)}.
	 * @since 3.0.0
	 */
	public void setRawValue(String value) {
		if (_value == null)
			throw new UnsupportedOperationException("native content");
		_value.setRawValue(value);
	}

	/** Evaluates the value to an Object.
	 * Note: it does NOT call {@link #isEffective} and it doesn't coerce
	 * the result (i.e., Object.class is assumed).
	 */
	public Object getValue(Component comp) {
		if (_value != null)
			return _value.getValue(_evalr, comp);

		Desktop desktop = comp.getDesktop();
		Page page;
		if (desktop == null) {
			Execution exec = Executions.getCurrent();
			if (exec == null)
				throw new IllegalStateException("Not attached, nor execution");
			desktop = exec.getDesktop();
			page = ((ExecutionCtrl)exec).getCurrentPage();
		} else {
			page = comp.getPage();
		}
		return ((WebAppCtrl)desktop.getWebApp()).getUiEngine()
			.getNativeContent(comp, _navval.getChildren(),
			((Native)_navval.newInstance(page, comp)).getHelper());
	}
	/** Evaluates the value to an Object.
	 * Note: it does NOT call {@link #isEffective} and it doesn't coerce
	 * the result (i.e., Object.class is assumed).
	 * @exception UnsupportedOperationException if value is the native content,
	 * i.e., it is constructed by use of {@link #Property(EvaluatorRef, String, NativeInfo, ConditionImpl)}.
	 */
	public Object getValue(Page page) {
		if (_value == null)
			throw new UnsupportedOperationException("native content");
		return _value.getValue(_evalr, page);
	}
	/** Assigns the value of this member to the specified component.
	 *
	 * 

Note: this method does nothing if {@link #isEffective} returns false. */ public void assign(Component comp) { if (isEffective(comp)) { try { assign0(comp); } catch (Exception ex) { log.error("Failed to assign "+this+" to "+comp+"\n"+Exceptions.getMessage(ex)); throw UiException.Aide.wrap(ex); } } } private Object[] resolve(Class cls) { Method mtd = null; Method[] mtds = null; final String mtdnm = Classes.toMethodName(_name, "set"); if (_value == null) { mtd = resolveMethod0(cls, mtdnm); } else { mtds = Classes.getCloseMethods(cls, mtdnm, new Class[] {null}); if (mtds.length == 0) { if (!DynamicPropertied.class.isAssignableFrom(cls)) throw new PropertyNotFoundException("Method "+mtdnm+" not found for "+cls); mtds = null; } else if (mtds.length == 1) { mtd = mtds[0]; mtds = null; } } return new Object[] {mtd, mtds}; } private void assign0(Component comp) throws Exception { //Note: we have to synchronize since metainfo is shared //(unless it is initialized at the constructor) final Class cls = comp.getClass(); if (_lastcls == null) { //first tiime synchronized (this) { if (_lastcls == null) { //not being initialized final Object[] mi = resolve(cls); _mtd = (Method)mi[0]; _mtds = (Method[])mi[1]; _lastcls = cls; } } } Method mtd; Method[] mtds; if (_lastcls == cls) { mtd = _mtd; mtds = _mtds; } else { //two or more different compopent classes (use="${x?a:b}") //We don't cache methods for 2nd class (only cache 1st) final Object[] mi = resolve(cls); mtd = (Method)mi[0]; mtds = (Method[])mi[1]; } //Note: if mtd and mtds are both null, it must be dyna-attr //However, if dyna-attr, mtd or mtds might not be null Class type = mtd != null ? mtd.getParameterTypes()[0]: Object.class; if (_value != null) _value.setExpectedType(type); Object val = getValue(comp); Method m = null; if (mtd != null) { m = mtd; } else if (mtds == null) { //it must be dynamic attribute ((DynamicPropertied)comp).setDynamicProperty(_name, val); return; //done } else { //mtds != null && val != null if ((m = findExact(mtds, val)) == null && (m = findAssignable(mtds, val)) == null && (m = findNullable(mtds, val)) == null) { //primitive if (val != null) for (int j = 0; j < mtds.length; ++j) { type = mtds[j].getParameterTypes()[0]; if (type.isPrimitive()) { try { val = Classes.coerce(type, val); m = mtds[j]; break; } catch (Throwable ex) { } } } if (m == null) { //non primitive, non string Method strmtd = null; for (int j = 0; j < mtds.length; ++j) { type = mtds[j].getParameterTypes()[0]; if (String.class.equals(type)) { strmtd = mtds[j]; } else if (!type.isPrimitive()) { try { val = Classes.coerce(type, val); m = mtds[j]; break; } catch (Throwable ex) { } } } if (m == null) { if (strmtd != null) { try { val = Classes.coerce(String.class, val); m = strmtd; } catch (Throwable ex) { } } if (m == null) throw new ClassCastException("Unable to find a setter named "+_name+" that supports "+val); } } } } m.invoke(comp, val); } private static Method findExact(final Method[] mtds, final Object val) { if (val != null) { final Class vcls = val.getClass(); for (int j = 0; j < mtds.length; ++j) if (vcls.equals(mtds[j].getParameterTypes()[0])) return mtds[j]; //found } return null; } private static Method findAssignable(final Method[] mtds, final Object val) { if (val != null) { //Look for the most 'extended' and isInstance class Method m = null; Class t = null; for (int j = 0; j < mtds.length; ++j) { final Class type = mtds[j].getParameterTypes()[0]; if (type.isInstance(val) && (t == null || t.isAssignableFrom(type))) { t = type; m = mtds[j]; } } return m; } return null; } private static Method findNullable(final Method[] mtds, final Object val) { if (val == null) { //Look for the most 'extended' class Method m = null; Class t = null; for (int j = 0; j < mtds.length; ++j) { final Class type = mtds[j].getParameterTypes()[0]; if (!type.isPrimitive() && (t == null || t.isAssignableFrom(type))) { t = type; m = mtds[j]; } } return m; } return null; } public String toString() { return "["+_name+(_navval != null ? ""+_navval: "="+_value)+']'; } //static utilities// /** Resolves the method for the specified property, or null * if {@link DynamicPropertied#setDynamicProperty} shall be used instead. * *

Use this method to retrieve the method when you want to assign a value * to a component's property. * *

Don't use the reflection directly since this method searches * more signatures. * @param name the property name, such as "title". * @exception PropertyNotFoundException if the property is not found, * i.e., no corresponding method and {@link DynamicPropertied} not * implemented. * @since 3.5.0 */ public static final Method resolveMethod(Class cls, String name) throws PropertyNotFoundException { return resolveMethod0(cls, Classes.toMethodName(name, "set")); } private static final Method resolveMethod0(Class cls, String mtdnm) throws PropertyNotFoundException { try { return Classes.getCloseMethod( cls, mtdnm, new Class[] {String.class}); } catch (NoSuchMethodException ex) { try { return Classes.getCloseMethod( cls, mtdnm, new Class[] {null}); } catch (NoSuchMethodException e2) { if (!DynamicPropertied.class.isAssignableFrom(cls)) throw new PropertyNotFoundException("Method, "+mtdnm+", not found for "+cls); return null; } } } /** Assigns a property. * *

Don't use the reflection directly since this method searches * more signatures. * * @exception PropertyNotFoundException if the property is not found, * i.e., no corresponding method and {@link DynamicPropertied} not * implemented. * @exception UiException if fail to assign * @since 3.5.0 */ public static final void assign(Component comp, String name, String value) { final Method mtd = resolveMethod(comp.getClass(), name); if (mtd != null) { try { Object val = Classes.coerce(mtd.getParameterTypes()[0], value); mtd.invoke(comp, val); } catch (Exception ex) { log.error("Failed to assign "+value+" to "+comp+"\n"+Exceptions.getMessage(ex)); throw UiException.Aide.wrap(ex); } } else { ((DynamicPropertied)comp).setDynamicProperty(name, value); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy