/*******************************************************************************
* 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.media;
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 android.content.Context;
import com.pixate.freestyle.styling.PXRuleSet;
import com.pixate.freestyle.styling.PXStyleUtils;
import com.pixate.freestyle.styling.PXStylesheet.PXStyleSheetOrigin;
import com.pixate.freestyle.styling.adapters.PXStyleAdapter;
import com.pixate.freestyle.styling.selectors.PXTypeSelector;
import com.pixate.freestyle.styling.selectors.PXSpecificity.PXSpecificityType;
import com.pixate.freestyle.util.CollectionUtil;
import com.pixate.freestyle.util.StringUtil;
public class PXMediaGroup implements PXMediaExpression {
private PXStyleSheetOrigin origin;
private PXMediaExpression query;
private List<PXRuleSet> ruleSets;
private Map<String, List<PXRuleSet>> ruleSetsByElementName;
private Map<String, List<PXRuleSet>> ruleSetsById;
private Map<String, List<PXRuleSet>> ruleSetsByClass;
private List<PXRuleSet> uncategorizedRuleSets;
/**
* Initializer a newly allocated instance
*
* @param query The medai query for this group
* @param origin The stylesheet origin for this group
*/
public PXMediaGroup(PXMediaExpression query, PXStyleSheetOrigin origin) {
this.query = query;
this.origin = origin;
}
/**
* Returns an non-mutable array of rule sets that are contained within this
* stylesheet
*/
public List<PXRuleSet> getRuleSets() {
return (ruleSets != null) ? new ArrayList<PXRuleSet>(ruleSets) : null;
}
public List<PXRuleSet> getRuleSets(Object styleable) {
List<PXRuleSet> result = new ArrayList<PXRuleSet>();
Set<PXRuleSet> items = new HashSet<PXRuleSet>();
// gather keys
PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable);
String elementName = styleAdapter.getElementName(styleable);
String styleId = styleAdapter.getStyleId(styleable);
String styleClass = styleAdapter.getStyleClass(styleable);
// find relevant ruleSets by element name
if (ruleSetsByElementName != null && !StringUtil.isEmpty(elementName)) {
List<PXRuleSet> rs = ruleSetsByElementName.get(elementName);
if (rs != null) {
for (PXRuleSet ruleSet : rs) {
if (!items.contains(ruleSet)) {
result.add(ruleSet);
items.add(ruleSet);
}
}
}
}
// find relevant ruleSets by id
if (ruleSetsById != null && !StringUtil.isEmpty(styleId)) {
List<PXRuleSet> rs = ruleSetsById.get(styleId);
if (rs != null) {
for (PXRuleSet ruleSet : rs) {
if (!items.contains(ruleSet)) {
result.add(ruleSet);
items.add(ruleSet);
}
}
}
}
// find relevant ruleSets by class
if (ruleSetsByClass != null && !StringUtil.isEmpty(styleClass)) {
String[] styleClasses = PXStyleUtils.PATTERN_WHITESPACE_PLUS.split(styleClass);
for (String aClass : styleClasses) {
List<PXRuleSet> rs = ruleSetsByClass.get(aClass);
if (rs != null) {
for (PXRuleSet ruleSet : rs) {
if (!items.contains(ruleSet)) {
result.add(ruleSet);
items.add(ruleSet);
}
}
}
}
}
// fallback to all uncategorized ruleSets. Note that these are already
// included in the element name, id, and class lists above, so we only
// need to add these if we didn't find any of those
if (result.isEmpty()) {
result = uncategorizedRuleSets;
}
return result;
}
public void addRuleSet(PXRuleSet ruleSet, Map<String, List<PXRuleSet>> partition, String key) {
List<PXRuleSet> ruleSets = partition.get(key);
// create ruleset array if we don't have one already
if (ruleSets == null) {
ruleSets = new ArrayList<PXRuleSet>();
// be sure to include in this array all uncategorized ruleSets that
// came before this one
if (uncategorizedRuleSets != null) {
ruleSets.addAll(uncategorizedRuleSets);
}
// save the ruleSet array back to the partition dictionary
partition.put(key, ruleSets);
}
// add this ruleSet to the ruleSet array associated with the given key
ruleSets.add(ruleSet);
}
/**
* Add a new rule set to this stylesheet
*
* @param ruleSet The rule set to add. null values are ignored
*/
public void addRuleSet(PXRuleSet ruleSet) {
if (ruleSet != null) {
if (ruleSets == null) {
ruleSets = new ArrayList<PXRuleSet>();
}
this.ruleSets.add(ruleSet);
// set origin specificity
ruleSet.setSpecificity(PXSpecificityType.ORIGIN, origin.ordinal());
// setup lookup by element type
PXTypeSelector typeSelector = ruleSet.getTargetTypeSelector();
String elementName = (typeSelector == null || typeSelector.hasUniversalType()) ? null
: typeSelector.getTypeName();
String styleId = (typeSelector == null) ? null : typeSelector.getStyleId();
List<String> styleClasses = (typeSelector == null) ? null : typeSelector
.getStyleClasses();
boolean added = false;
// NOTE: nesting if-statements to avoid walking type selector
// expressions for id and classes when not needed
if (elementName != null && !"*".equals(elementName)) {
if (ruleSetsByElementName == null) {
ruleSetsByElementName = new HashMap<String, List<PXRuleSet>>();
}
addRuleSet(ruleSet, ruleSetsByElementName, elementName);
added = true;
}
if (!StringUtil.isEmpty(styleId)) {
if (ruleSetsById == null) {
ruleSetsById = new HashMap<String, List<PXRuleSet>>();
}
addRuleSet(ruleSet, ruleSetsById, styleId);
added = true;
}
if (!CollectionUtil.isEmpty(styleClasses)) {
if (ruleSetsByClass == null) {
ruleSetsByClass = new HashMap<String, List<PXRuleSet>>();
}
for (String styleClass : styleClasses) {
addRuleSet(ruleSet, ruleSetsByClass, styleClass);
}
added = true;
}
// if this wasn't added to any of our partitions, then we need to
// collect it into the uncategorized partition
// and add it to all other partitions to preserve rule set order in
// those sets as well
if (!added) {
if (uncategorizedRuleSets == null) {
uncategorizedRuleSets = new ArrayList<PXRuleSet>();
}
uncategorizedRuleSets.add(ruleSet);
// add uncategorized ruleSets to all partitions
if (ruleSetsByElementName != null) {
for (String key : ruleSetsByElementName.keySet()) {
List<PXRuleSet> items = ruleSetsByElementName.get(key);
items.add(ruleSet);
}
}
if (ruleSetsById != null) {
for (String key : ruleSetsById.keySet()) {
List<PXRuleSet> items = ruleSetsById.get(key);
items.add(ruleSet);
}
}
if (ruleSetsByClass != null) {
for (String key : ruleSetsByClass.keySet()) {
List<PXRuleSet> items = ruleSetsByClass.get(key);
items.add(ruleSet);
}
}
}
}
}
/**
* Returns a PXStylesheetOrigin value indicating the origin of this
* stylesheet. Origin values are used in specificity calculations.
*/
public PXStyleSheetOrigin getOrigin() {
return origin;
}
/**
* Returns the media query associated with this grouping of rule sets
*/
public PXMediaExpression getQuery() {
return query;
}
/*
* (non-Javadoc)
* @see
* com.pixate.freestyle.styling.media.PXMediaExpression#matches(android.content
* .Context)
*/
public boolean matches(Context context) {
// assume null means "true" to cover non-media-query groups
return (query != null) ? query.matches(context) : true;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
// TODO: wrap in @media with query
List<String> parts = new ArrayList<String>();
if (query != null) {
parts.add(String.format("@media %s {", query.toString()));
for (PXRuleSet ruleSet : ruleSets) {
parts.add(String.format(" %s", ruleSet.toString()));
}
parts.add("}");
} else {
for (PXRuleSet ruleSet : ruleSets) {
parts.add(ruleSet.toString());
}
}
return StringUtil.join(parts, "\n");
}
}