blob: 4f266849a30b7fe7e62a0c5b6a0af01f56a89437 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.royale.compiler.internal.css.codegen;
import static org.apache.royale.compiler.internal.as.codegen.ABCGeneratingReducer.pushNumericConstant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.instructionlist.InstructionList;
import org.apache.royale.abc.semantics.MethodBodyInfo;
import org.apache.royale.abc.semantics.MethodInfo;
import org.apache.royale.abc.semantics.Name;
import org.apache.royale.abc.visitors.IABCVisitor;
import org.apache.royale.abc.visitors.IMethodBodyVisitor;
import org.apache.royale.abc.visitors.IMethodVisitor;
import org.apache.royale.abc.visitors.ITraitsVisitor;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.css.ICSSDocument;
import org.apache.royale.compiler.css.ICSSNode;
import org.apache.royale.compiler.css.ICSSProperty;
import org.apache.royale.compiler.css.ICSSPropertyValue;
import org.apache.royale.compiler.css.ICSSRule;
import org.apache.royale.compiler.css.ICSSSelector;
import org.apache.royale.compiler.css.ICSSSelectorCondition;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.definitions.references.ReferenceFactory;
import org.apache.royale.compiler.internal.as.codegen.LexicalScope;
import org.apache.royale.compiler.internal.css.CSSArrayPropertyValue;
import org.apache.royale.compiler.internal.css.CSSColorPropertyValue;
import org.apache.royale.compiler.internal.css.CSSFontFace;
import org.apache.royale.compiler.internal.css.CSSFunctionCallPropertyValue;
import org.apache.royale.compiler.internal.css.CSSKeywordPropertyValue;
import org.apache.royale.compiler.internal.css.CSSMultiValuePropertyValue;
import org.apache.royale.compiler.internal.css.CSSNumberPropertyValue;
import org.apache.royale.compiler.internal.css.CSSRgbColorPropertyValue;
import org.apache.royale.compiler.internal.css.CSSRgbaColorPropertyValue;
import org.apache.royale.compiler.internal.css.CSSRule;
import org.apache.royale.compiler.internal.css.CSSSelector;
import org.apache.royale.compiler.internal.css.CSSStringPropertyValue;
import org.apache.royale.compiler.internal.css.codegen.Pair.InstructionListAndClosure;
import org.apache.royale.compiler.internal.css.codegen.Pair.InstructionListAndString;
import org.apache.royale.compiler.internal.css.codegen.Pair.PairOfInstructionLists;
import org.apache.royale.compiler.internal.css.semantics.CSSSemanticAnalyzer;
import org.apache.royale.compiler.internal.units.EmbedCompilationUnit;
import org.apache.royale.compiler.problems.CSSCodeGenProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.projects.IRoyaleProject;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* This reducer generates a valid CSS file from a CSS model tree. The target
* code has two main parts - an array of selector data and an object literal
* with function closures to set the style properties.
*/
public class CSSReducer implements ICSSCodeGenResult
{
/**
* The {@code global} CSS selector.
*/
private static final String GLOBAL_SELECTOR = "global";
/**
* The base name of CSS FontFace list.
*/
public static final String FONTFACE_ARRAY = "fontFaces";
/**
* The base name of CSS Factory Functions.
*/
public static final String FACTORY_FUNCTIONS = "factoryFunctions";
/**
* The base name of CSS Inheriting Styles.
*/
public static final String INHERITING_STYLES = "inheritingStyles";
/**
* The base name of CSS data array.
*/
public static final String DATA_ARRAY = "data";
/**
* AET name for {@code var inheiritingStyles:String}.
*/
private static final Name NAME_INHERITING_STYLES = new Name(INHERITING_STYLES);
/**
* AET name for {@code var data:Array}.
*/
public static final Name NAME_DATA_ARRAY = new Name(DATA_ARRAY);
/**
* AET name for {@code var data:Array}.
*/
public static final Name NAME_FONTFACE_ARRAY = new Name(FONTFACE_ARRAY);
/**
* ABC {@code Name} for<br>
* <code>public static var factoryFunctions:Object = generateFactoryFunctions();</code>
*/
public static final Name NAME_FACTORY_FUNCTIONS = new Name(FACTORY_FUNCTIONS);
/**
* Parameter types for a method without any parameters.
*/
private static final Vector<Name> EMPTY_PARAM_TYPES = new Vector<Name>();
/**
* Create a CSS reducer.
*
* @param project Owner project.
* @param cssDocument CSS DOM tree.
* @param abcVisitor {@link IABCVisitor} to which generated ABC constructs
* are added.
* @param session CSS compilation session data.
* @param isDefaultFactory If true, the generated code will register the
* styles with {@link ICSSRuntimeConstants#DEFAULT_FACTORY}.
*/
public CSSReducer(final IRoyaleProject project,
final ICSSDocument cssDocument,
final IABCVisitor abcVisitor,
final CSSCompilationSession session,
final boolean isDefaultFactory,
final int styleTagIndex)
{
assert project != null : "Expected a Flex project.";
assert cssDocument != null : "Expected a CSS model.";
assert abcVisitor != null : "Expected an ABC visitor.";
assert session != null : "Expected a CSSCompilationSession.";
this.styleTagIndex = styleTagIndex;
this.problems = new HashSet<ICompilerProblem>();
this.session = session;
this.resolvedSelectors = ImmutableMap.copyOf(session.resolvedSelectors);
this.abcVisitor = abcVisitor;
this.project = project;
if (isDefaultFactory)
this.factory = ICSSRuntimeConstants.DEFAULT_FACTORY;
else
this.factory = ICSSRuntimeConstants.FACTORY;
}
/**
* Stores index used to uniquely identify style blocks.
*/
private final int styleTagIndex;
/**
* Stores CSS semantic analysis results.
*/
private final CSSCompilationSession session;
/**
* CSS code generation problems.
*/
private final Set<ICompilerProblem> problems;
/**
* A dictionary for "selector" to "class definition".
*/
private final ImmutableMap<ICSSSelector, String> resolvedSelectors;
/**
* Populate the target ABC instructions by using this visitor.
*/
private final IABCVisitor abcVisitor;
/**
* Instructions for the class init method of a generated style's class.
*/
private InstructionList cinitInstructionList;
/**
* Owner project.
*/
private final IRoyaleProject project;
/**
* The "factory" with which the styles will be registered.
*/
private final Integer factory;
/**
* The media query string building up for the selector
*/
private String mediaQueryString;
/**
* The list of fontfaces
*/
private ArrayList<String> fontFaces = new ArrayList<String>();
/**
* The map of media query to factory functions
*/
private HashMap<String,ArrayList<String>> mediaQueryMap = new HashMap<String, ArrayList<String>>();
/**
* Root reduction rule. It aggregates all the instructions and emit ABC code
* of {@code StyleDateClass}.
*
* @param site {@link ICSSDocument} node.
* @param namespaceList Instructions to create array and closure for
* namespace declarations.
* @param ruleList Instructions to create array and closure for rules.
* @return Instructions to create array and closure for the CSS document.
*/
public PairOfInstructionLists reduceDocument(ICSSNode site, PairOfInstructionLists namespaceList, PairOfInstructionLists ruleList)
{
// Instructions to push an array object on the stack.
final int elementSize = ruleList.arrayReduction.getInstructions().size();
final InstructionList arrayInstructions = new InstructionList();
arrayInstructions.addAll(ruleList.arrayReduction);
arrayInstructions.addInstruction(ABCConstants.OP_newarray, elementSize);
final PairOfInstructionLists pair = new PairOfInstructionLists(arrayInstructions, ruleList.closureReduction);
generateABC(pair);
return pair;
}
/**
* Generate ABC instructions for both array and closure.
*
* @param pair Instructions to create array and closure for the
* {@code StyleDataClass}.
*/
private void generateABC(final PairOfInstructionLists pair)
{
assert cinitInstructionList == null : "generateABC should only be called once per reducer because each document should only be reduced once.";
cinitInstructionList = new InstructionList();
// Generate instructions for "StyleDataClass$cinit()".
final InstructionList initializeFactoryFunctions = cinitInstructionList;
if (styleTagIndex == 0)
{
// Initialize "factoryFunctions".
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
initializeFactoryFunctions.addAll(pair.closureReduction);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty, NAME_FACTORY_FUNCTIONS);
// Initialize "data".
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
initializeFactoryFunctions.addAll(pair.arrayReduction);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty, NAME_DATA_ARRAY);
// Initialize "fontFaces".
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
for (String fontFace: fontFaces)
{
initializeFactoryFunctions.addInstruction(ABCConstants.OP_pushstring, fontFace);
}
if (fontFaces.size() > 0)
initializeFactoryFunctions.addInstruction(ABCConstants.OP_newarray, fontFaces.size());
else
initializeFactoryFunctions.addInstruction(ABCConstants.OP_pushnull);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty, NAME_FONTFACE_ARRAY);
// Initialize "inheritingStyles".
@SuppressWarnings("unused")
final String inheritingStylesText =
Joiner.on(",").skipNulls().join(session.inheritingStyles);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_pushnull);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty, NAME_INHERITING_STYLES);
}
else
{
// Initialize "factoryFunctions".
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
initializeFactoryFunctions.addAll(pair.closureReduction);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty,
new Name(FACTORY_FUNCTIONS + Integer.toString(styleTagIndex)));
// Initialize "data".
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
initializeFactoryFunctions.addAll(pair.arrayReduction);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty,
new Name(DATA_ARRAY + Integer.toString(styleTagIndex)));
// Initialize "fontFaces".
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
for (String fontFace: fontFaces)
{
initializeFactoryFunctions.addInstruction(ABCConstants.OP_pushstring, fontFace);
}
if (fontFaces.size() > 0)
initializeFactoryFunctions.addInstruction(ABCConstants.OP_newarray, fontFaces.size());
else
initializeFactoryFunctions.addInstruction(ABCConstants.OP_pushnull);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty,
new Name(FONTFACE_ARRAY + Integer.toString(styleTagIndex)));
// Initialize "inheritingStyles".
@SuppressWarnings("unused")
final String inheritingStylesText =
Joiner.on(",").skipNulls().join(session.inheritingStyles);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_getlocal0);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_pushnull);
initializeFactoryFunctions.addInstruction(ABCConstants.OP_initproperty,
new Name(INHERITING_STYLES + Integer.toString(styleTagIndex)));
}
}
@Override
public InstructionList getClassInitializationInstructions()
{
assert cinitInstructionList != null : "The initialize instructions may not be accessed until the document reduction has executed";
return cinitInstructionList;
}
@Override
public void visitClassTraits(ITraitsVisitor classTraitsVisitor)
{
// Resolve "Object" type.
final IResolvedQualifiersReference referenceObject = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), "Object");
// Resolve "String" type.
@SuppressWarnings("unused")
final IResolvedQualifiersReference referenceString = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), "String");
// Resolve "Array" type.
final IResolvedQualifiersReference referenceArray = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), "Array");
// Generate "public static var factoryFunctions:Object;"
classTraitsVisitor.visitSlotTrait(
ABCConstants.TRAIT_Const,
NAME_FACTORY_FUNCTIONS,
ITraitsVisitor.RUNTIME_SLOT,
referenceObject.getMName(),
LexicalScope.noInitializer);
// Generate "public static var data:Array;"
classTraitsVisitor.visitSlotTrait(
ABCConstants.TRAIT_Const,
NAME_DATA_ARRAY,
ITraitsVisitor.RUNTIME_SLOT,
referenceArray.getMName(),
LexicalScope.noInitializer);
// Generate "public static var inheritingStyles:Array"
classTraitsVisitor.visitSlotTrait(
ABCConstants.TRAIT_Var,
NAME_INHERITING_STYLES,
ITraitsVisitor.RUNTIME_SLOT,
referenceArray.getMName(),
LexicalScope.noInitializer);
}
/**
* Namespace node does not generate code.
*/
public PairOfInstructionLists reduceNamespaceDefinition(ICSSNode site)
{
return null;
}
/**
* Namespace list node does not generate code.
*/
public PairOfInstructionLists reduceNamespaceList(ICSSNode site, List<PairOfInstructionLists> namespaces)
{
return null;
}
/**
* Reduce a CSS property.
* <p>
* For example: {@code fontSize : 12; } will be translated into
* ActionScript:<br>
* <code>this.fontSize = 12;</code><br>
* The ABC instructions for this statement are:<br>
*
* <pre>
* getlocal0
* pushint 12
* setproperty fontSize
* </pre>
*
* {@code getlocal0} will put "this" on the stack, then the property value
* is put on the stack. Finally, {@code setproperty} will assign "12" to
* "fontSize".
* <p>
* The code generation is based on the assumption that the CSS DOM tree has
* been semantically checked, so that the validity of the property name and
* type is not re-checked in the BURM.
*/
public PairOfInstructionLists reduceProperty(ICSSNode site)
{
assert site instanceof ICSSProperty : "Expected ICSSProperty node but got " + site.getClass().getName();
final ICSSProperty propertyNode = (ICSSProperty)site;
final String name = propertyNode.getName();
final ICSSPropertyValue value = propertyNode.getValue();
final InstructionList inst = new InstructionList();
final InstructionList valueInstructions = getInstructionListForPropertyValue(value);
if (!valueInstructions.isEmpty())
{
// push "this" on the stack
inst.addInstruction(ABCConstants.OP_getlocal0);
// push value on the stack
inst.addAll(valueInstructions);
// set the property value
inst.addInstruction(ABCConstants.OP_setproperty, new Name(name));
}
return new PairOfInstructionLists(new InstructionList(), inst);
}
/**
* Get the AET instructions that can push values for the property on the AVM
* stack.
*
* @param value CSS property value.
* @return AET instructions.
*/
private InstructionList getInstructionListForPropertyValue(final ICSSPropertyValue value)
{
final InstructionList valueInstructions = new InstructionList();
// push property value on the stack
if (value instanceof CSSStringPropertyValue)
{
valueInstructions.addInstruction(ABCConstants.OP_pushstring, ((CSSStringPropertyValue)value).getValue());
}
else if (value instanceof CSSColorPropertyValue)
{
valueInstructions.addInstruction(ABCConstants.OP_pushint, new Integer(((CSSColorPropertyValue)value).getColorAsInt()));
}
else if (value instanceof CSSRgbColorPropertyValue)
{
valueInstructions.addInstruction(ABCConstants.OP_pushint, new Integer(((CSSRgbColorPropertyValue)value).getColorAsInt()));
}
else if (value instanceof CSSRgbaColorPropertyValue)
{
valueInstructions.addInstruction(ABCConstants.OP_pushuint, new Long(((CSSRgbaColorPropertyValue)value).getColorAsLong()));
}
else if (value instanceof CSSKeywordPropertyValue)
{
CSSKeywordPropertyValue keywordValue = (CSSKeywordPropertyValue)value;
String keywordString = keywordValue.getKeyword();
if (IASLanguageConstants.TRUE.equals(keywordString))
valueInstructions.addInstruction(ABCConstants.OP_pushtrue);
else if (IASLanguageConstants.FALSE.equals(keywordString))
valueInstructions.addInstruction(ABCConstants.OP_pushfalse);
else
valueInstructions.addInstruction(ABCConstants.OP_pushstring, ((CSSKeywordPropertyValue)value).getKeyword());
}
else if (value instanceof CSSNumberPropertyValue)
{
CSSNumberPropertyValue numValue = (CSSNumberPropertyValue)value;
if (numValue.getUnit().equals("%"))
valueInstructions.addInstruction(ABCConstants.OP_pushstring, numValue.toString());
else
valueInstructions.addInstruction(ABCConstants.OP_pushdouble, new Double(numValue.getNumber().doubleValue()));
}
else if (value instanceof CSSFunctionCallPropertyValue)
{
final CSSFunctionCallPropertyValue functionCall = (CSSFunctionCallPropertyValue)value;
if ("ClassReference".equals(functionCall.name))
{
final String className = CSSFunctionCallPropertyValue.getSingleArgumentFromRaw(functionCall.rawArguments);
if ("null".equals(className))
{
// ClassReference(null) resets the property's class reference.
valueInstructions.addInstruction(ABCConstants.OP_pushnull);
}
else
{
final IResolvedQualifiersReference reference = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), className);
valueInstructions.addInstruction(ABCConstants.OP_getlex, reference.getMName());
}
}
else if ("url".equals(functionCall.name))
{
final String urlString = CSSFunctionCallPropertyValue.getSingleArgumentFromRaw(functionCall.rawArguments);
valueInstructions.addInstruction(ABCConstants.OP_pushstring, urlString);
}
else if ("PropertyReference".equals(functionCall.name))
{
// TODO: implement me
}
else if ("Embed".equals(functionCall.name))
{
final EmbedCompilationUnit embedCompilationUnit = session.resolvedEmbedProperties.get(functionCall);
if (embedCompilationUnit == null)
{
final ICompilerProblem e = new CSSCodeGenProblem(
new IllegalStateException("Unable to find compilation unit for " + functionCall));
problems.add(e);
}
else
{
final String qName = embedCompilationUnit.getName();
final IResolvedQualifiersReference reference = ReferenceFactory.packageQualifiedReference(project.getWorkspace(), qName);
valueInstructions.addInstruction(ABCConstants.OP_getlex, reference.getMName());
}
}
else
{
if (project.isRoyale())
{
valueInstructions.addInstruction(ABCConstants.OP_pushstring, functionCall.toString());
}
else
{
assert false : "CSS parser bug: unexpected function call property value: " + functionCall;
throw new IllegalStateException("Unexpected function call property value: " + functionCall);
}
}
}
else if (value instanceof CSSArrayPropertyValue)
{
final CSSArrayPropertyValue arrayValue = (CSSArrayPropertyValue)value;
for (final ICSSPropertyValue elementValue : arrayValue.getElements())
{
valueInstructions.addAll(getInstructionListForPropertyValue(elementValue));
}
valueInstructions.addInstruction(ABCConstants.OP_newarray, arrayValue.getElements().size());
}
else if (value instanceof CSSMultiValuePropertyValue)
{
final CSSMultiValuePropertyValue arrayValue = (CSSMultiValuePropertyValue)value;
for (final ICSSPropertyValue elementValue : arrayValue.getElements())
{
valueInstructions.addAll(getInstructionListForPropertyValue(elementValue));
}
valueInstructions.addInstruction(ABCConstants.OP_newarray, arrayValue.getElements().size());
}
else
{
assert false : "Unsupported property value: " + value;
}
return valueInstructions;
}
/**
* Reduce a property list node. This method aggregates all the instructions
* to set property values. It also add instructions to setup the stack frame
* for this function closure.
*/
public PairOfInstructionLists reducePropertyList(ICSSNode site, List<PairOfInstructionLists> properties)
{
final InstructionList closureInstructions = new InstructionList();
for (final PairOfInstructionLists inst : properties)
closureInstructions.addAll(inst.closureReduction);
closureInstructions.addInstruction(ABCConstants.OP_returnvoid);
return new PairOfInstructionLists(null, closureInstructions);
}
/**
* Reduce CSS rule node. The ABC method for the closures are generated here
* by merging the instructions from {@code propertyList} into the
* {@code selector}'s closure map.
*
* @param site {@link ICSSRule} node.
* @param selector Instructions to construct the array data, and a map of
* named closures.
* @param propertyList Instructions to create array and closure that set the
* properties.
* @return Instructions for array data and a map of closures.
*/
public InstructionListAndClosure reduceRule(ICSSNode site, InstructionListAndClosure selector, PairOfInstructionLists propertyList)
{
// Generate anonymous function.
final MethodInfo methodInfo = new MethodInfo();
String miName = ((CSSRule)site).getSelectorGroup().get(0).getElementName();
if (mediaQueryString != null)
{
pushNumericConstant(ICSSRuntimeConstants.MEDIA_QUERY, selector.arrayReduction);
selector.arrayReduction.addInstruction(ABCConstants.OP_pushstring, mediaQueryString);
miName = mediaQueryString + "_" + miName;
if (mediaQueryMap.containsKey(mediaQueryString))
{
ArrayList<String> factoryList = mediaQueryMap.get(mediaQueryString);
factoryList.add(miName);
}
else
{
ArrayList<String> factoryList = new ArrayList<String>();
factoryList.add(miName);
mediaQueryMap.put(mediaQueryString, factoryList);
}
}
methodInfo.setMethodName(miName);
methodInfo.setParamTypes(EMPTY_PARAM_TYPES);
final IMethodVisitor methodVisitor = abcVisitor.visitMethod(methodInfo);
methodVisitor.visit();
// Generate method body.
final MethodBodyInfo methodBodyInfo = new MethodBodyInfo();
methodBodyInfo.setMethodInfo(methodInfo);
final IMethodBodyVisitor methodBodyVisitor = methodVisitor.visitBody(methodBodyInfo);
methodBodyVisitor.visit();
methodBodyVisitor.visitInstructionList(propertyList.closureReduction);
methodBodyVisitor.visitEnd();
// Finish anonymous function.
methodVisitor.visitEnd();
Set<String> keySet = selector.closureReduction.keySet();
// make a copy of the keySet so the loop can modify
// the keys.
ArrayList<String> keyList = new ArrayList<String>();
keyList.addAll(keySet);
// Populate the closure name-body map with method info objects.
for (final String name : keyList)
{
if (mediaQueryString != null)
{
selector.closureReduction.remove(name);
selector.closureReduction.put(mediaQueryString + "_" + name,
methodInfo);
}
else
selector.closureReduction.put(name, methodInfo);
}
mediaQueryString = null;
return selector;
}
/**
* Reduce a "selector" node. The node can have zero or more ascendant
* selectors.
*/
public InstructionListAndString reduceSelector(ICSSNode site)
{
assert site instanceof ICSSSelector : "Expected a 'selector' node, but got '" + site.getClass().getName() + "'.";
final InstructionList arrayInstructions = new InstructionList();
final List<String> resolvedSimpleSelectorNames = new ArrayList<String>();
final ICSSSelector selectorNode = (ICSSSelector)site;
final ImmutableList<ICSSSelector> selectors = CSSSelector.getCombinedSelectorList(selectorNode);
for (final ICSSSelector selector : selectors)
{
final String selectorLiteral = getSelecterLiteralForABC(selector);
// Generate array data for conditional selector.
for (final ICSSSelectorCondition condition : selector.getConditions())
{
pushNumericConstant(ICSSRuntimeConstants.CONDITION, arrayInstructions);
arrayInstructions.addInstruction(ABCConstants.OP_pushstring, condition.getConditionType().name().toLowerCase());
arrayInstructions.addInstruction(ABCConstants.OP_pushstring, condition.getValue());
}
// Generate array data for type selector.
pushNumericConstant(ICSSRuntimeConstants.SELECTOR, arrayInstructions);
arrayInstructions.addInstruction(ABCConstants.OP_pushstring, selectorLiteral);
// Collect resolved name for the simple selector in the combined selectors.
// For example: "spark.components.Button#loginButton", ".highlight#main:up"
final String resolvedSelectorName = selectorLiteral.concat(Joiner.on("").join(selector.getConditions()));
resolvedSimpleSelectorNames.add(resolvedSelectorName);
}
final String combinedSelectors = Joiner.on(" ").join(resolvedSimpleSelectorNames);
return new InstructionListAndString(arrayInstructions, combinedSelectors);
}
/**
* Convert from CSS type names to ActionScript QNames.
*
* @param selector CSS selector.
* @return String value of the selector name that works with Flex CSS
* runtime.
*/
private String getSelecterLiteralForABC(final ICSSSelector selector)
{
final String selectorQname;
if (project.getCSSManager().isFlex3CSS())
{
assert !selector.isAdvanced() : "Advanced selector is not supported in Flex 3 mode. " + selector;
// Flex 3 style CSS are emitted "as-is".
final String elementName = selector.getElementName();
selectorQname = elementName == null ? "" : elementName;
}
else if (CSSSemanticAnalyzer.isWildcardSelector(selector))
{
// Selectors without specified types are normalized to have wildcard type "*".
// From SDK 4.5.2 and up, the CSS runtime queries such selectors by Qname "*".
if (GLOBAL_SELECTOR.equals(selector.getElementName()))
selectorQname = GLOBAL_SELECTOR;
else
selectorQname = "";
}
else
{
final String qname = resolvedSelectors.get(selector);
// commented out this assert. Seems like it too strict for when someone has multiple type selectors on a single ruleset
//assert qname != null : "Unable to resolve type selector: " + selector;
if (qname == null)
selectorQname = selector.getElementName();
else
selectorQname = qname;
}
return selectorQname;
}
/**
* Reduce rule list node.
*/
public PairOfInstructionLists reduceRuleList(ICSSNode site, List<InstructionListAndClosure> rules)
{
final InstructionList arrayInstructions = new InstructionList();
final InstructionList closureInstructions = new InstructionList();
int closureCount = 0;
for (final InstructionListAndClosure ruleReduction : rules)
{
arrayInstructions.addAll(ruleReduction.arrayReduction);
for (final Entry<String, MethodInfo> entry : ruleReduction.closureReduction.entrySet())
{
closureInstructions.addInstruction(ABCConstants.OP_pushstring, entry.getKey());
closureInstructions.addInstruction(ABCConstants.OP_newfunction, entry.getValue());
closureCount++;
}
}
closureInstructions.addInstruction(ABCConstants.OP_newobject, closureCount);
return new PairOfInstructionLists(arrayInstructions, closureInstructions);
}
/**
* Reduce instructions for selector group.
* <p>
* The array reduction is instruction list. The closure reduction is a map.
* The keys in the map are the resolved selector names that will be used as
* names for the generated closures. The values in the map are all null.
* They will be populated in the parent tree, because the {@link MethodInfo}
* for the closure bodies come from a sibling "properties" tree.
*/
public InstructionListAndClosure reduceSelectorGroup(ICSSNode site, List<InstructionListAndString> selectors)
{
final InstructionList arrayInstructions = new InstructionList();
final Map<String, MethodInfo> closureNames = new HashMap<String, MethodInfo>();
for (final InstructionListAndString selectorReduction : selectors)
{
arrayInstructions.addAll(selectorReduction.arrayReduction);
closureNames.put(selectorReduction.closureReduction, null);
}
pushNumericConstant(ICSSRuntimeConstants.STYLE_DECLARATION, arrayInstructions);
pushNumericConstant(factory, arrayInstructions);
return new InstructionListAndClosure(arrayInstructions, closureNames);
}
public PairOfInstructionLists reduceFontFaceList(ICSSNode site, List<PairOfInstructionLists> fontFaces)
{
// TODO Implement @font-face code generation
return null;
}
public PairOfInstructionLists reduceFontFace(ICSSNode site)
{
CSSFontFace fontFace = (CSSFontFace)site;
String fontFaceSource = fontFace.getSourceValue();
fontFaces.add(fontFaceSource);
return null;
}
public PairOfInstructionLists reduceMediaQuery(ICSSNode site, List<PairOfInstructionLists> conditions)
{
// TODO Implement @media code generation
return null;
}
public PairOfInstructionLists reduceMediaQueryCondition(ICSSNode site)
{
// TODO Implement @media code generation
if (mediaQueryString == null)
mediaQueryString = site.toString();
else if (mediaQueryString.endsWith("only"))
mediaQueryString += " " + site.toString();
else if (site.toString().equals(","))
mediaQueryString += ",";
else if (mediaQueryString.endsWith(","))
mediaQueryString += " " + site.toString();
else
mediaQueryString += " and " + site.toString();
return null;
}
/**
* @return CSS reduction problems.
*/
public Set<ICompilerProblem> getProblems()
{
return problems;
}
}