zk/src/main/java/org/zkoss/zk/ui/select/impl/ComponentLocalProperties.java
/**
*
*/
package org.zkoss.zk.ui.select.impl;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.ShadowElement;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.sys.ComponentCtrl;
/**
* A collection of utilities that check local properties of Components.
* Including type, ID, class, attribute, and pseudo class.
* @since 6.0.0
* @author simonpai
*/
public class ComponentLocalProperties {
/**
* Returns true if the selector matches the given component. Combinators
* are not allowed.
* @param component
* @param selector
*/
public static boolean match(Component component, String selector) {
return match(component, selector, null);
}
/**
* Returns true if the selector matches the given component. Combinators
* are not allowed.
* @param component
* @param selector
* @param defs
*/
public static boolean match(Component component, String selector, Map<String, PseudoClassDef> defs) {
List<Selector> selectorList = new Parser().parse(selector);
ComponentMatchCtx ctx = new ComponentMatchCtx(component, selectorList);
for (Selector s : selectorList) {
if (s.size() > 1)
continue;
if (match(ctx, s.get(0), defs))
return true;
}
return false;
}
/*package*/ static boolean match(ComponentMatchCtx context, SimpleSelectorSequence seq,
Map<String, PseudoClassDef> defs) {
Component comp = context.getComponent();
return matchType(comp, seq.getType()) && matchID(comp, seq.getId()) && matchClasses(comp, seq.getClasses())
&& matchAttributes(comp, seq.getAttributes())
&& matchPseudoClasses(context, seq.getPseudoClasses(), defs)
&& matchPseudoElements(comp, seq.getPseudoElements());
}
/*package*/ static boolean matchID(Component component, String id) {
if (id == null)
return true;
return id.equals(component.getId());
}
/*package*/ static boolean matchType(Component component, String type) {
if (type == null)
return true;
return component.getDefinition().getName().toLowerCase(java.util.Locale.ENGLISH)
.equals(type.toLowerCase(java.util.Locale.ENGLISH));
}
/*package*/ static boolean matchClasses(Component component, Set<String> classes) {
if (classes == null || classes.isEmpty())
return true;
if (!(component instanceof HtmlBasedComponent))
return false;
String scls = ((HtmlBasedComponent) component).getSclass();
String zcls = ((HtmlBasedComponent) component).getZclass();
for (String c : classes)
if (scls == null || !scls.matches("(?:^|.*\\s)" + c + "(?:\\s.*|$)") && !Objects.equals(zcls, c))
return false;
return true;
}
/*package*/ static boolean matchAttributes(Component component, List<Attribute> attributes) {
if (attributes == null || attributes.isEmpty())
return true;
for (Attribute attr : attributes)
if (!matchValue(getValue(component, attr.getName()), attr))
return false;
return true;
}
/*package*/ static boolean matchPseudoClasses(Component component, List<PseudoClass> pseudoClasses,
Map<String, PseudoClassDef> defs) {
if (pseudoClasses == null || pseudoClasses.isEmpty())
return true;
return matchPseudoClasses(new ComponentMatchCtx(component), pseudoClasses, defs);
}
/*package*/ static boolean matchPseudoClasses(ComponentMatchCtx context, List<PseudoClass> pseudoClasses,
Map<String, PseudoClassDef> defs) {
if (pseudoClasses == null || pseudoClasses.isEmpty())
return true;
for (PseudoClass pc : pseudoClasses) {
PseudoClassDef def = getPseudoClassDef(defs, pc.getName());
if (def == null)
throw new UiException("Pseudo class definition not found: " + pc.getName());
String[] param = pc.getParameter();
if (param == null ? !def.accept(context) : !def.accept(context, pc.getParameter()))
return false;
}
return true;
}
private static boolean matchPseudoElements(Component comp, List<PseudoElement> pseudoElements) {
if (pseudoElements == null || pseudoElements.isEmpty())
return true;
if (comp instanceof ShadowElement) {
for (PseudoElement pe : pseudoElements) {
if ("shadow".equals(pe.getName())) {
Component parent = ((ShadowElement) comp).getShadowHost();
if (parent != null) {
ComponentCtrl parentCtrl = (ComponentCtrl) parent;
if (parentCtrl.getShadowRoots().contains(comp)) {
return true;
}
}
}
}
}
return false;
}
// helper //
private static Object getValue(Component component, String name) {
try {
return component.getClass().getMethod("get" + capitalize(name)).invoke(component);
} catch (NoSuchMethodException e) {
// no such method
} catch (SecurityException e) {
// SecurityManager doesn't like you
} catch (IllegalAccessException e) {
// attempted to call a non-public method
} catch (InvocationTargetException e) {
// exception thrown by the getter method
}
try {
return component.getClass().getMethod("is" + capitalize(name)).invoke(component);
} catch (NoSuchMethodException e) {
// no such method
} catch (SecurityException e) {
// SecurityManager doesn't like you
} catch (IllegalAccessException e) {
// attempted to call a non-public method
} catch (InvocationTargetException e) {
// exception thrown by the getter method
}
// try dynamic attribute
return component.getAttribute(name);
}
private static String capitalize(String str) {
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}
private static PseudoClassDef getPseudoClassDef(Map<String, PseudoClassDef> defs, String className) {
PseudoClassDef def = null;
if (defs != null && !defs.isEmpty())
def = defs.get(className);
if (def != null)
return def;
return BasicPseudoClassDefs.getDefinition(className);
}
private static Object parseData(String source, Class<?> expectedType) {
// TODO: enhance type support
if (expectedType.equals(Integer.class))
return new Integer(source);
if (expectedType.equals(Boolean.class))
return new Boolean(source);
if (expectedType.equals(Double.class))
return new Double(source);
return source;
}
private static boolean matchValue(Object value, Attribute attr) {
switch (attr.getOperator()) {
case BEGIN_WITH:
return value != null && value.toString().startsWith(attr.getValue());
case END_WITH:
return value != null && value.toString().endsWith(attr.getValue());
case CONTAIN:
return value != null && value.toString().contains(attr.getValue());
case EQUAL:
default:
try {
Object attrValue = parseData(attr.getValue(), attr.isQuoted() ? String.class : value.getClass());
return Objects.equals(value, attrValue);
} catch (Exception e) {
// failed to convert attribute value to expected type
return false;
}
}
}
}