/******************************************************************************* * Copyright 2012-present Pixate, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.pixate.freestyle.styling.cache; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.pixate.freestyle.styling.PXDeclaration; import com.pixate.freestyle.styling.PXRuleSet; import com.pixate.freestyle.styling.PXStyleUtils; import com.pixate.freestyle.styling.adapters.PXStyleAdapter; import com.pixate.freestyle.styling.selectors.PXTypeSelector; import com.pixate.freestyle.styling.stylers.PXStyler; import com.pixate.freestyle.styling.stylers.PXStylerContext; import com.pixate.freestyle.util.CollectionUtil; import com.pixate.freestyle.util.ObjectUtil; import com.pixate.freestyle.util.PXColorUtil; import com.pixate.freestyle.util.PXDrawableUtil; import com.pixate.freestyle.util.PXLog; import com.pixate.freestyle.util.StringUtil; public class PXStyleInfo { /** * The default style key value. For example, the {@link PXDrawableUtil} and * the {@link PXColorUtil} assign values to their state maps for this * default style. */ public static final String DEFAULT_STYLE = "default"; private Map<String, List<PXDeclaration>> declarationsByState; private Map<String, Set<PXStyler>> stylersByState; private String styleKey; /** * Constructs a new {@link PXStyleInfo}. * * @param styleKey */ public PXStyleInfo(String styleKey) { this.styleKey = styleKey; } public Set<String> getStates() { return declarationsByState != null ? declarationsByState.keySet() : null; } public void addDeclarations(List<PXDeclaration> declarations, String stateName) { if (stateName != null && !CollectionUtil.isEmpty(declarations)) { if (declarationsByState == null) { declarationsByState = new HashMap<String, List<PXDeclaration>>(); } // TODO: check for pre-existing? declarationsByState.put(stateName, declarations); } } public void addStylers(Set<PXStyler> stylers, String stateName) { if (stateName != null && !CollectionUtil.isEmpty(stylers)) { if (stylersByState == null) { stylersByState = new HashMap<String, Set<PXStyler>>(); } stylersByState.put(stateName, stylers); } } public List<PXDeclaration> getDeclarations(String stateName) { return (declarationsByState != null) ? declarationsByState.get(stateName) : null; } public Set<PXStyler> getStylers(String stateName) { return (stylersByState != null) ? stylersByState.get(stateName) : null; } public void applyTo(Object styleable) { PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable); // abort application of style info if the styleable's style key does not // match the info's style key if (!ObjectUtil.areEqual(styleKey, styleAdapter.getStyleKey(styleable))) { if (PXLog.isLogging()) { PXLog.w(PXStyleInfo.class.getSimpleName(), "StyleKey mismatch (%s != %s). Aborted applyStyleInfo for %s", styleKey, styleAdapter.getStyleId(styleable), PXStyleUtils.getDescriptionForStyleable(styleable)); } return; } List<PXStyler> stylers = styleAdapter.getStylers(); Map<String, PXStyler> stylersByProperty = styleAdapter.getStylersByProperty(); Set<String> states = getStates(); if (states == null) { return; } List<PXRuleSet> ruleSets = new ArrayList<PXRuleSet>(states.size()); List<PXStylerContext> contexts = new ArrayList<PXStylerContext>(states.size()); for (String stateName : states) { List<PXDeclaration> activeDeclarations = getDeclarations(stateName); // No need for Android (note: this is not a View invalidate() // equivalent) // if (forceInvalidation) { // PXStyleUtils.invalidateStyleable(styleable); // } if (true /* * FIXME always true for now ![PXStyleUtils * stylesOfStyleable:styleable * matchDeclarations:activeDeclarations state:stateName] */) { Set<PXStyler> activeStylers = getStylers(stateName); // create context and store styleable and state name there int styleHash = 17 * PXStyleAdapter.getStyleAdapter(styleable).getBounds(styleable).hashCode() + activeDeclarations.hashCode(); PXStylerContext context = new PXStylerContext(styleable, stateName, styleHash); // process declarations in styler order for (PXStyler currentStyler : stylers) { if (activeStylers != null && activeStylers.contains(currentStyler)) { // process the declarations, in order for (PXDeclaration declaration : activeDeclarations) { PXStyler styler = stylersByProperty.get(declaration.getName()); if (styler == currentStyler) { styler.processDeclaration(declaration, context); } } // apply styler completion block currentStyler.applyStylesWithContext(context); } } // see if there's a catch-all 'updateStyleWithRuleSet:context:' // method to call PXRuleSet ruleSet = new PXRuleSet(); for (PXDeclaration declaration : activeDeclarations) { ruleSet.addDeclaration(declaration); } // Collect the items that will be sent to the Adapter's // updateStyle ruleSets.add(ruleSet); contexts.add(context); } } // Batch update. styleAdapter.updateStyle(ruleSets, contexts); } @Override public String toString() { List<String> parts = new ArrayList<String>(); Set<String> states = getStates(); if (states != null) { for (String state : states) { String stateName = (state.length() > 0) ? state : "default"; // emit state parts.add(String.format("%s {", stateName)); // emit declaration List<PXDeclaration> declarations = getDeclarations(state); if (declarations != null) { for (PXDeclaration declaration : declarations) { parts.add(String.format(" %s", declaration.toString())); } } // close parts.add("}"); } } return CollectionUtil.toString(parts, "\n"); } /** * Creates and returns a {@link PXStyleInfo} for a given styleable. * * @param styleable * @return a {@link PXStyleInfo} (may be <code>null</code>) */ public static PXStyleInfo getStyleInfo(Object styleable) { PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable); PXStyleInfo result = new PXStyleInfo(styleAdapter.getStyleKey(styleable)); // find all rule sets that apply to this styleable List<PXRuleSet> ruleSets = PXStyleUtils.getMatchingRuleSets(styleable); if (!CollectionUtil.isEmpty(ruleSets)) { // remove pseudo-element rule sets List<PXRuleSet> toRemove = new ArrayList<PXRuleSet>(); for (PXRuleSet ruleSet : ruleSets) { PXTypeSelector selector = ruleSet.getTargetTypeSelector(); if (!StringUtil.isEmpty(selector.getPseudoElement())) { toRemove.add(ruleSet); } } ruleSets.removeAll(toRemove); } // process by state if (!CollectionUtil.isEmpty(ruleSets)) { // grab a list of supported pseudo-classes for this styleable object List<String> pseudoClasses = styleAdapter.getSupportedPseudoClasses(styleable); // style pseudo-classes if (!CollectionUtil.isEmpty(pseudoClasses)) { for (String pseudoClass : pseudoClasses) { // filter the list of rule sets to only those that specify // the current state List<PXRuleSet> ruleSetsForState = PXStyleUtils.filterRuleSets(ruleSets, styleable, pseudoClass); if (!CollectionUtil.isEmpty(ruleSetsForState)) { setStyleInfo(result, ruleSetsForState, styleable, pseudoClass); } } } else { setStyleInfo(result, ruleSets, styleable, DEFAULT_STYLE); } } return (!CollectionUtil.isEmpty(result.getStates())) ? result : null; } private static void setStyleInfo(PXStyleInfo styleInfo, List<PXRuleSet> ruleSets, Object styleable, String stateName) { // merge all rule sets into a single rule set based on origin and // weight/specificity PXRuleSet mergedRuleSet = PXRuleSet.mergeRuleSets(ruleSets); PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable); List<PXStyler> stylers = styleAdapter.getStylers(); Map<String, PXStyler> stylersByProperty = styleAdapter.getStylersByProperty(); // build a set of stylers that are active based on the property names we // have in the merged rule set Set<PXStyler> activeStylers = new HashSet<PXStyler>(); // keep track of active declarations List<PXDeclaration> activeDeclarations = new ArrayList<PXDeclaration>(); for (PXDeclaration declaration : mergedRuleSet.getDeclarations()) { PXStyler styler = stylersByProperty.get(declaration.getName()); // In case the styler is null, we also check if the adapter supports // it. This is important for the Node tests. if (styler != null || !styleAdapter.isSupportingStylers()) { activeDeclarations.add(declaration); if (styler != null) { activeStylers.add(styler); } } else if (stylers == null) { activeDeclarations.add(declaration); } } styleInfo.addDeclarations(activeDeclarations, stateName); styleInfo.addStylers(activeStylers, stateName); } }