/** * */ 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; } } } }