Return-Path: X-Original-To: apmail-flex-commits-archive@www.apache.org Delivered-To: apmail-flex-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id D9BEB113B7 for ; Tue, 22 Jul 2014 13:35:27 +0000 (UTC) Received: (qmail 44824 invoked by uid 500); 22 Jul 2014 13:35:27 -0000 Delivered-To: apmail-flex-commits-archive@flex.apache.org Received: (qmail 44636 invoked by uid 500); 22 Jul 2014 13:35:27 -0000 Mailing-List: contact commits-help@flex.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@flex.apache.org Delivered-To: mailing list commits@flex.apache.org Received: (qmail 44504 invoked by uid 99); 22 Jul 2014 13:35:27 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Jul 2014 13:35:27 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 457219AF1EF; Tue, 22 Jul 2014 13:35:27 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: cdutz@apache.org To: commits@flex.apache.org Date: Tue, 22 Jul 2014 13:35:29 -0000 Message-Id: <610ed4b121fe4ea18c1312c305a949cb@git.apache.org> In-Reply-To: <9b4488ac636245f08eb096463c80c652@git.apache.org> References: <9b4488ac636245f08eb096463c80c652@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [03/51] [partial] - Migrated the directory structure to a more Maven style structure. - Started migrating the Parser from Antlr2+3 and JFlex to Antlr4. http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/CSSReducer.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/CSSReducer.java b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/CSSReducer.java new file mode 100644 index 0000000..db63df3 --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/CSSReducer.java @@ -0,0 +1,691 @@ +/* + * + * 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.flex.compiler.internal.css.codegen; + +import static org.apache.flex.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.flex.abc.ABCConstants; +import org.apache.flex.abc.instructionlist.InstructionList; +import org.apache.flex.abc.semantics.MethodBodyInfo; +import org.apache.flex.abc.semantics.MethodInfo; +import org.apache.flex.abc.semantics.Name; +import org.apache.flex.abc.visitors.IABCVisitor; +import org.apache.flex.abc.visitors.IMethodBodyVisitor; +import org.apache.flex.abc.visitors.IMethodVisitor; +import org.apache.flex.abc.visitors.ITraitsVisitor; +import org.apache.flex.compiler.constants.IASLanguageConstants; +import org.apache.flex.compiler.css.ICSSDocument; +import org.apache.flex.compiler.css.ICSSNode; +import org.apache.flex.compiler.css.ICSSProperty; +import org.apache.flex.compiler.css.ICSSPropertyValue; +import org.apache.flex.compiler.css.ICSSRule; +import org.apache.flex.compiler.css.ICSSSelector; +import org.apache.flex.compiler.css.ICSSSelectorCondition; +import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference; +import org.apache.flex.compiler.definitions.references.ReferenceFactory; +import org.apache.flex.compiler.internal.as.codegen.LexicalScope; +import org.apache.flex.compiler.internal.css.CSSArrayPropertyValue; +import org.apache.flex.compiler.internal.css.CSSColorPropertyValue; +import org.apache.flex.compiler.internal.css.CSSFunctionCallPropertyValue; +import org.apache.flex.compiler.internal.css.CSSKeywordPropertyValue; +import org.apache.flex.compiler.internal.css.CSSNumberPropertyValue; +import org.apache.flex.compiler.internal.css.CSSRgbColorPropertyValue; +import org.apache.flex.compiler.internal.css.CSSRule; +import org.apache.flex.compiler.internal.css.CSSSelector; +import org.apache.flex.compiler.internal.css.CSSStringPropertyValue; +import org.apache.flex.compiler.internal.css.codegen.Pair.InstructionListAndClosure; +import org.apache.flex.compiler.internal.css.codegen.Pair.InstructionListAndString; +import org.apache.flex.compiler.internal.css.codegen.Pair.PairOfInstructionLists; +import org.apache.flex.compiler.internal.css.semantics.CSSSemanticAnalyzer; +import org.apache.flex.compiler.internal.units.EmbedCompilationUnit; +import org.apache.flex.compiler.problems.CSSCodeGenProblem; +import org.apache.flex.compiler.problems.ICompilerProblem; +import org.apache.flex.compiler.projects.IFlexProject; +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"; + + /** + * AET name for {@code var inheiritingStyles:String}. + */ + private static final Name NAME_INHERITING_STYLES = new Name("inheritingStyles"); + + /** + * AET name for {@code var data:Array}. + */ + public static final Name NAME_DATA_ARRAY = new Name("data"); + + /** + * ABC {@code Name} for
+ * public static var factoryFunctions:Object = generateFactoryFunctions(); + */ + public static final Name NAME_FACTORY_FUNCTIONS = new Name("factoryFunctions"); + + /** + * Parameter types for a method without any parameters. + */ + private static final Vector EMPTY_PARAM_TYPES = new Vector(); + + /** + * 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 IFlexProject project, + final ICSSDocument cssDocument, + final IABCVisitor abcVisitor, + final CSSCompilationSession session, + final boolean isDefaultFactory) + { + 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.problems = new HashSet(); + 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 CSS semantic analysis results. + */ + private final CSSCompilationSession session; + + /** + * CSS code generation problems. + */ + private final Set problems; + + /** + * A dictionary for "selector" to "class definition". + */ + private final ImmutableMap 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 IFlexProject 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 map of media query to factory functions + */ + private HashMap> mediaQueryMap = new HashMap>(); + + /** + * 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; + + // 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 "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); + } + + @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 namespaces) + { + return null; + } + + /** + * Reduce a CSS property. + *

+ * For example: {@code fontSize : 12; } will be translated into + * ActionScript:
+ * this.fontSize = 12;
+ * The ABC instructions for this statement are:
+ * + *

+     * getlocal0
+     * pushint 12
+     * setproperty fontSize
+     * 
+ * + * {@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". + *

+ * 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 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) + { + valueInstructions.addInstruction(ABCConstants.OP_pushdouble, new Double(((CSSNumberPropertyValue)value).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 + { + 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 + { + 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 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 factoryList = mediaQueryMap.get(mediaQueryString); + factoryList.add(miName); + } + else + { + ArrayList factoryList = new ArrayList(); + 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(); + + // Populate the closure name-body map with method info objects. + for (final String name : selector.closureReduction.keySet()) + { + 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 resolvedSimpleSelectorNames = new ArrayList(); + final ICSSSelector selectorNode = (ICSSSelector)site; + final ImmutableList 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); + assert qname != null : "Unable to resolve type selector: " + selector; + selectorQname = qname; + } + return selectorQname; + } + + /** + * Reduce rule list node. + */ + public PairOfInstructionLists reduceRuleList(ICSSNode site, List 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 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. + *

+ * 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 selectors) + { + final InstructionList arrayInstructions = new InstructionList(); + final Map closureNames = new HashMap(); + 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 fontFaces) + { + // TODO Implement @font-face code generation + return null; + } + + public PairOfInstructionLists reduceFontFace(ICSSNode site) + { + // TODO Implement @font-face code generation + return null; + } + + public PairOfInstructionLists reduceMediaQuery(ICSSNode site, List 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 + mediaQueryString += "and " + site.toString(); + return null; + } + + /** + * @return CSS reduction problems. + */ + public Set getProblems() + { + return problems; + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSCodeGenResult.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSCodeGenResult.java b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSCodeGenResult.java new file mode 100644 index 0000000..7bddedf --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSCodeGenResult.java @@ -0,0 +1,45 @@ +/* + * + * 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.flex.compiler.internal.css.codegen; + +import org.apache.flex.abc.instructionlist.InstructionList; +import org.apache.flex.abc.visitors.ITraitsVisitor; + +/** + * Interface to results of the CSS code generator. + */ +public interface ICSSCodeGenResult +{ + /** + * @return The {@link InstructionList} containing instructions for + * initializing the CSS data structures. This method does not append + * {@code returnvoid} instructions to the end of the instruction list. + */ + InstructionList getClassInitializationInstructions(); + + /** + * Add generated data slots for generated data structures to the specified + * {@link ITraitsVisitor}. + * + * @param classTraitsVisitor {@link ITraitsVisitor} to which traits for the + * css data structures are added. + */ + void visitClassTraits(ITraitsVisitor classTraitsVisitor); +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSRuntimeConstants.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSRuntimeConstants.java b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSRuntimeConstants.java new file mode 100644 index 0000000..ef6bc8d --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/ICSSRuntimeConstants.java @@ -0,0 +1,44 @@ +/* + * + * 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.flex.compiler.internal.css.codegen; + +import org.apache.flex.abc.instructionlist.InstructionList; + +/** + * Defines all the runtime constants in the CSS framework. The constants are of + * type {@code Integer} so that they can be directly used in + * {@link InstructionList#addInstruction(int, Object)}. + */ +public interface ICSSRuntimeConstants +{ + // From CSSClass + static final Integer SELECTOR = 0; + static final Integer CONDITION = 1; + static final Integer STYLE_DECLARATION = 2; + static final Integer MEDIA_QUERY = 3; + + // From CSSFactory + static final Integer DEFAULT_FACTORY = 0; + static final Integer FACTORY = 1; + + // From CSSDataType + static final Integer NATIVE = 0; + static final Integer DEFINITION = 1; +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/Pair.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/Pair.java b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/Pair.java new file mode 100644 index 0000000..18cace7 --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/codegen/Pair.java @@ -0,0 +1,101 @@ +/* + * + * 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.flex.compiler.internal.css.codegen; + +import java.util.Map; + +import org.apache.flex.abc.instructionlist.InstructionList; +import org.apache.flex.abc.semantics.MethodInfo; + +/** + * The CSS code generation produces two sets of ABC instructions - one for the + * array data, the other for the closure functions. This is a two-tuple used by + * {@link CSSReducer}. At different tree nodes, the reduction result types for + * array and closure can be different. This generic class allows variant types + * to be used when reducing different tree nodes. + * + * @param Type of the array reduction result. + * @param Type of the closure reduction result. + */ +abstract class Pair +{ + /** + * Create a two-tuple object. + * + * @param arrayReduction The array reduction result. + * @param closureReduction The closure reduction result. + */ + protected Pair(L arrayReduction, R closureReduction) + { + this.arrayReduction = arrayReduction; + this.closureReduction = closureReduction; + } + + /** Array reduction result. */ + public final L arrayReduction; + + /** Closure reduction result. */ + public final R closureReduction; + + /** + * Print debugging message for this {@code Pair} object. + */ + @Override + public String toString() + { + return String.format("[array]\n%s\n\n[closure]\n%s\n", arrayReduction, closureReduction); + } + + /** + * Both the array reduction and the closure reduction results are + * {@link InstructionList}. + */ + public static final class PairOfInstructionLists extends Pair + { + public PairOfInstructionLists(InstructionList arrayReduction, InstructionList closureReduction) + { + super(arrayReduction, closureReduction); + } + } + + /** + * The array reduction result is {@link InstructionList}. The closure result + * is {@code String}. + */ + public static final class InstructionListAndString extends Pair + { + public InstructionListAndString(InstructionList arrayReduction, String closureReduction) + { + super(arrayReduction, closureReduction); + } + } + + /** + * The array reduction result is {@link InstructionList}. The closure result + * is a map of closure names to closure method bodies. + */ + public static final class InstructionListAndClosure extends Pair> + { + public InstructionListAndClosure(InstructionList arrayReduction, Map closureReduction) + { + super(arrayReduction, closureReduction); + } + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/package.hmtl ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/package.hmtl b/compiler/src/main/java/org/apache/flex/compiler/internal/css/package.hmtl new file mode 100644 index 0000000..a5119d5 --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/package.hmtl @@ -0,0 +1,37 @@ + + + + + +This package is the internal extension of the external +org.apache.flex.compiler.css package. + +

+It contains classes that implement the CSS model interfaces. +

+ +

+See the description +of the org.apache.flex.compiler package for an explanation +of how the compiler code is organized into external and internal packages. +

+ + + http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/ActivatedStyleSheets.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/ActivatedStyleSheets.java b/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/ActivatedStyleSheets.java new file mode 100644 index 0000000..2bb2436 --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/ActivatedStyleSheets.java @@ -0,0 +1,150 @@ +/* + * + * 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.flex.compiler.internal.css.semantics; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.flex.compiler.css.ICSSDocument; +import org.apache.flex.compiler.internal.projects.LibraryPathManager; +import org.apache.flex.utils.FilenameNormalization; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * A container for all activated CSS models. It can sort the CSS models by + * priority. + *
    + *
  1. CSS files from {@code defaults-css-files} configuration option.
  2. + *
  3. "defaults.css" in SWC libraries on the library paths.
  4. + *
  5. Theme CSS and SWC.
  6. + *
+ */ +public class ActivatedStyleSheets +{ + public ActivatedStyleSheets() + { + defaults = new ArrayList(); + themes = new ArrayList(); + libraries = new HashMap(); + comparator = new Comparator() + { + /** + * Sort CSS model in their SWC filenames' alphabetic order. If we + * need to sort in the define order of library path, + * {@link LibraryPathManager} need to provide the comparator. + */ + @Override + public int compare(ICSSDocument o1, ICSSDocument o2) + { + final String swcFile1 = libraries.get(o1); + final String swcFile2 = libraries.get(o2); + return swcFile1.compareTo(swcFile2); + } + }; + } + + /** + * CSS models from {@code defaults-css-files} option. + */ + private final List defaults; + + /** + * CSS models from theme files. + */ + private final List themes; + + /** + * CSS models from "defaults.css" files in SWC libraries. + */ + private final Map libraries; + + /** + * Sort {@link #libraries} on their paths by alphabetical order. + */ + private final Comparator comparator; + + /** + * Activate a default CSS model. + * + * @param css CSS model. + */ + public void addDefaultCSS(final ICSSDocument css) + { + assert css != null : "defaults css can't be null"; + defaults.add(css); + } + + /** + * Activate a theme CSS model. + * + * @param css CSS model. + */ + public void addThemeCSS(final ICSSDocument css) + { + assert css != null : "theme css can't be null"; + themes.add(css); + } + + /** + * Activate a library CSS model. + * + * @param css CSS model. + * @param path Filename of SWC library that contains the CSS. + */ + public void addLibraryCSS(final ICSSDocument css, final String path) + { + assert css != null : "librarycss can't be null"; + libraries.put(css, FilenameNormalization.normalize(path)); + } + + /** + * @return A list of all activated CSS model sorted by precedence. + */ + public List sort() + { + // Sort CSS models from SWC libraries by library path order. + final List librariesSorted = new ArrayList(libraries.keySet()); + Collections.sort(librariesSorted, comparator); + + final ImmutableList.Builder builder = new ImmutableList.Builder(); + builder.addAll(defaults); + builder.addAll(librariesSorted); + builder.addAll(themes); + return builder.build(); + } + + /** + * @return All activated CSS models. + */ + public Set all() + { + final ImmutableSet.Builder builder = new ImmutableSet.Builder(); + builder.addAll(defaults); + builder.addAll(libraries.keySet()); + builder.addAll(themes); + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/CSSSemanticAnalyzer.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/CSSSemanticAnalyzer.java b/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/CSSSemanticAnalyzer.java new file mode 100644 index 0000000..a4748f4 --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/css/semantics/CSSSemanticAnalyzer.java @@ -0,0 +1,759 @@ +/* + * + * 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.flex.compiler.internal.css.semantics; + +import static org.apache.flex.compiler.internal.css.CSSStringPropertyValue.stripQuotes; +import static com.google.common.collect.Collections2.filter; +import static com.google.common.collect.Collections2.transform; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.flex.compiler.common.XMLName; +import org.apache.flex.compiler.constants.IASLanguageConstants; +import org.apache.flex.compiler.css.ICSSDocument; +import org.apache.flex.compiler.css.ICSSNamespaceDefinition; +import org.apache.flex.compiler.css.ICSSProperty; +import org.apache.flex.compiler.css.ICSSPropertyValue; +import org.apache.flex.compiler.css.ICSSRule; +import org.apache.flex.compiler.css.ICSSSelector; +import org.apache.flex.compiler.definitions.IClassDefinition; +import org.apache.flex.compiler.definitions.IDefinition; +import org.apache.flex.compiler.definitions.metadata.IMetaTag; +import org.apache.flex.compiler.internal.css.CSSFunctionCallPropertyValue; +import org.apache.flex.compiler.internal.css.CSSManager; +import org.apache.flex.compiler.internal.css.CSSSelector; +import org.apache.flex.compiler.internal.css.CSSStringPropertyValue; +import org.apache.flex.compiler.internal.css.codegen.CSSCompilationSession; +import org.apache.flex.compiler.internal.mxml.MXMLDialect; +import org.apache.flex.compiler.internal.parsing.as.ASParser; +import org.apache.flex.compiler.internal.projects.ASProject; +import org.apache.flex.compiler.internal.targets.Target; +import org.apache.flex.compiler.internal.tree.as.metadata.MetaTagsNode; +import org.apache.flex.compiler.internal.units.EmbedCompilationUnit; +import org.apache.flex.compiler.internal.units.EmbedCompilationUnitFactory; +import org.apache.flex.compiler.mxml.IXMLNameResolver; +import org.apache.flex.compiler.problems.CSSEmbedAssetProblem; +import org.apache.flex.compiler.problems.CSSExcludedStylePropertyProblem; +import org.apache.flex.compiler.problems.CSSUndefinedNamespacePrefixProblem; +import org.apache.flex.compiler.problems.CSSUndefinedTypeProblem; +import org.apache.flex.compiler.problems.CSSUnknownDefaultNamespaceProblem; +import org.apache.flex.compiler.problems.CSSUnresolvedClassReferenceProblem; +import org.apache.flex.compiler.problems.CSSUnusedTypeSelectorProblem; +import org.apache.flex.compiler.problems.ICompilerProblem; +import org.apache.flex.compiler.projects.ICompilerProject; +import org.apache.flex.compiler.projects.IFlexProject; +import org.apache.flex.compiler.tree.metadata.IMetaTagNode; +import org.apache.flex.compiler.units.ICompilationUnit; +import org.apache.flex.utils.FilenameNormalization; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.Collections2; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +/** + * Semantic analyzer for CSS. This is a static class. It's used by + * {@link CSSManager}. + */ +public class CSSSemanticAnalyzer +{ + + /** Global selector. */ + private static final String GLOBAL_SELECTOR = "global"; + /** Universal selector. */ + private static final String UNIVERSAL_SELECTOR = "*"; + + /** + * Try to resolve all the dependencies introduced by + * {@code ClassReference()} and {@code Embed()} property values in a CSS + * rule. + * + * @param resolvedEmbedProperties A map from {@code Embed()} property values + * to their resolved {@link EmbedCompilationUnit}'s. + * @param cssRule CSS rule. + * @param project Current project. + * @param classReferences Definitions of the {@code ClassReference("...")} + * properties will be stored in this collection after the function returns. + * @param embedCompilationUnits {@link EmbedCompilationUnit}'s used in the + * rules will be stored in this collection after the function returns. + * @param problems Compiler problems. This method reports + * {@link CSSUnresolvedClassReferenceProblem} issues. + */ + public static void resolveDependencies( + final Map resolvedEmbedProperties, + final ICSSRule cssRule, + final ICompilerProject project, + final Set classReferences, + final Set embedCompilationUnits, + final Collection problems) + { + assert cssRule != null : "CSS rule can't be null"; + assert project != null : "Project can't be null"; + assert problems != null : "Problems can't be null"; + assert classReferences != null : "Expected an output collection for ClassReferences."; + assert embedCompilationUnits != null : "Expected an output collection for Embed."; + + for (final ICSSProperty property : cssRule.getProperties()) + { + final ICSSPropertyValue propertyValue = property.getValue(); + if (propertyValue instanceof CSSFunctionCallPropertyValue) + { + final CSSFunctionCallPropertyValue functionCall = (CSSFunctionCallPropertyValue)propertyValue; + if (CSSFunctionCallPropertyValue.CLASS_REFERENCE.equals(functionCall.name)) + { + // Found a ClassReference() property. + if ("null".equals(functionCall.rawArguments)) + { + // Do nothing. ClassReference(null) resets the skin class. + } + else + { + final String qName; + if (CSSStringPropertyValue.isQuoted(functionCall.rawArguments)) + qName = stripQuotes(functionCall.rawArguments); + else + qName = functionCall.rawArguments; + + final IDefinition definition = project.resolveQNameToDefinition(qName); + // The definition is expected to be a class definition. + if (definition != null && definition instanceof IClassDefinition) + { + classReferences.add((IClassDefinition)definition); + } + else + { + final CSSUnresolvedClassReferenceProblem problem = new CSSUnresolvedClassReferenceProblem(functionCall); + problems.add(problem); + } + } + } + else if (CSSFunctionCallPropertyValue.EMBED.equals(functionCall.name)) + { + final String embedMetadata = String.format("[%s(%s)]", functionCall.name, functionCall.rawArguments); + // TODO Calling normalize here prevents an assert later + // in the getFileSpecification() of Workspace. The problem is that an embed + // in default.css inside a SWC has a source path which doesn't look normalized. + final String sourcePath = FilenameNormalization.normalize(functionCall.getSourcePath()); + final MetaTagsNode metadata = ASParser.parseMetadata(project.getWorkspace(), embedMetadata, + sourcePath, functionCall.getStart(), + functionCall.getLine(), functionCall.getColumn(), + problems); + final IMetaTagNode embedTag = metadata.getTagByName("Embed"); + if (embedTag == null) + { + problems.add(new CSSEmbedAssetProblem(functionCall)); + } + else + { + try + { + final EmbedCompilationUnit embedCompilationUnit = + EmbedCompilationUnitFactory.getCompilationUnit( + (ASProject)project, + embedTag.getSourcePath(), + functionCall, + embedTag.getAllAttributes(), + problems); + if (embedCompilationUnit == null) + { + problems.add(new CSSEmbedAssetProblem(functionCall)); + } + else + { + resolvedEmbedProperties.put(functionCall, embedCompilationUnit); + embedCompilationUnits.add(embedCompilationUnit); + } + } + catch (InterruptedException e) + { + // Incremental build interrupts the current build. We can + // throw away the results. + } + } + } + } + } + } + + /** + * Resolve type selectors to class definitions within a file scope context + *

+ * If a namespace short name is mapped to a undefined namespace URI, just + * ignore. + * + * @param xmlNameResolver XML name resolver + * @param css CSS DOM. + * @param problems Collect problems. + * @param isCompatibilityVersion3 If true, do not create + * {@link CSSUnknownDefaultNamespaceProblem}'s. + * @return A map of CSS selectors to QNames of their resolved types. + */ + public static ImmutableMap resolveSelectors( + final IXMLNameResolver xmlNameResolver, + final ICSSDocument css, + final Collection problems, + final boolean isCompatibilityVersion3) + { + assert xmlNameResolver != null : "Expected xmlNameResolver"; + assert css != null : "Expected CSS"; + + final ImmutableSet allSelectors = getAllSelectors(css); + + if (isCompatibilityVersion3) + return resolveSelectorsAsFlex3Style(allSelectors); + + final ICSSNamespaceDefinition defaultNamespace = css.getDefaultNamespaceDefinition(); + final Builder builder = new Builder(); + for (final ICSSSelector selector : allSelectors) + { + // Expand selector to QName and conditions. + if (isWildcardSelector(selector)) + continue; + + final String prefix = selector.getNamespacePrefix(); + final ICSSNamespaceDefinition namespace; + + if (prefix == null) + { + // Check if the selector is a type selector without explicit namespace. + if (defaultNamespace == null) + { + problems.add(new CSSUnknownDefaultNamespaceProblem((CSSSelector)selector)); + continue; + } + else + { + namespace = defaultNamespace; + } + } + else + { + // Resolve namespace. + namespace = css.getNamespaceDefinition(prefix); + } + + if (namespace == null) + { + problems.add(new CSSUndefinedNamespacePrefixProblem((CSSSelector)selector)); + continue; + } + + assert (selector.getElementName() != null) : "Null element name should be skipped as a wildcard selector."; + final XMLName xmlName = new XMLName(namespace.getURI(), selector.getElementName()); + + // Resolve type name. + final String qname = xmlNameResolver.resolveXMLNameToQualifiedName(xmlName, MXMLDialect.MXML_2009); + if (qname == null) + { + problems.add(new CSSUndefinedTypeProblem((CSSSelector)selector)); + } + else + { + builder.put(selector, qname); + } + } + return builder.build(); + } + + /** + * Resolve selectors as Flex 3 CSS. In Flex 3 CSS, selectors don't have + * namespaces. As a result, they don't have to be resolved to a type + * definition. For example, selector "Button" will be emitted as a selector + * literal "Button". Whereas, in Flex 4 mode, "Button" would be resolved to + * a default namespace and then emitted as a Spark Button QName or MX Button + * QName. + * + * @param selectors All the selectors to resolve. + * @return A map from selectors to its resolved runtime style selector name. + */ + private static ImmutableMap resolveSelectorsAsFlex3Style( + final Iterable selectors) + { + final ImmutableMap.Builder builder = new ImmutableMap.Builder(); + for (final ICSSSelector selector : selectors) + { + builder.put(selector, selector.getCSSSyntax()); + } + return builder.build(); + } + + /** + * Collect all the selectors in the CSS document including the subjects and + * the combination selectors. + * + * @param document CSS document + * @return All the selectors in the CSS. + */ + public static ImmutableSet getAllSelectors(final ICSSDocument document) + { + assert document != null : "Expected CSS document"; + + final ImmutableSet.Builder builder = new ImmutableSet.Builder(); + for (final ICSSRule rule : document.getRules()) + { + for (final ICSSSelector subject : rule.getSelectorGroup()) + { + ICSSSelector selector = subject; + while (selector != null) + { + builder.add(selector); + if (selector.getCombinator() != null) + selector = selector.getCombinator().getSelector(); + else + selector = null; + } + } + } + return builder.build(); + } + + /** + * A {@link Predicate} that filters {@link ICSSSelector}'s matched by a + * given set of class definitions. This is created for + * {@link Collections2#filter(Collection, Predicate)}. + */ + private static class MatchedCSSRulePredicate implements Predicate + { + /** + * QNames of the definitions to be matched by the CSS rules. + */ + private final ImmutableSet qnames; + + /** + * A map of selectors resolved to class definitions. + */ + private final ImmutableMap resolvedSelectors; + + /** + * Create a predicate for filtering matched CSS rules. + * + * @param qnames A set of class definitions to be matched by the + * CSS rules. + * @param resolvedSelectors A map of selectors resolved to class + * definitions. + */ + public MatchedCSSRulePredicate(final ImmutableSet qnames, + final ImmutableMap resolvedSelectors) + { + assert qnames != null : "Expected a set of definition for the CSS rules to match."; + assert resolvedSelectors != null : "Expected a map of selectors resolved to class definitions."; + this.qnames = qnames; + this.resolvedSelectors = resolvedSelectors; + } + + /** + * Return true if any of the subject selectors in the + * {@code rule}'s selector group match any definitions in + * {@link #qnames}. Combinator selectors are ignored. + */ + @Override + public boolean apply(final ICSSRule rule) + { + for (final ICSSSelector selector : rule.getSelectorGroup()) + { + if (isWildcardSelector(selector)) + return true; + final String qname = resolvedSelectors.get(selector); + if (qnames.contains(qname)) + return true; + } + return false; + } + } + + /** + * This predicate is created for {@code -compatibility-version=3} mode. In + * Flex 3, the selectors don't have namespace specifiers. Under the + * "compatible" mode, {@code Button} means {@code *|Button} in CSS3 syntax. + *

+ * All selectors with Flex 4 advanced syntax will be dropped. + *

+ * This class only compares the selector element names and the definition + * short names. + */ + private static class Flex3CSSRulePredicate implements Predicate + { + private final ImmutableSet definitionSimpleNames; + + private Flex3CSSRulePredicate(final ImmutableSet definitionSimpleNames) + { + this.definitionSimpleNames = definitionSimpleNames; + } + + @Override + public boolean apply(ICSSRule rule) + { + for (final ICSSSelector selector : rule.getSelectorGroup()) + { + // drop advanced selectors for flex 3 + if (selector.isAdvanced()) + return false; + + // drop unused css rules + final String elementName = selector.getElementName(); + if (GLOBAL_SELECTOR.equals(elementName)) + continue; + if (elementName == null) + continue; + if (!definitionSimpleNames.contains(elementName)) + return false; + } + return true; + } + } + + /** + * Convert a dot-separated QName string to the simple name. For example: + *

    + *
  • {@code f("a.b.foo") = "foo";}
  • + *
  • {@code f("bar") = "bar";}
  • + *
+ */ + private static final Function QNAME_TO_SIMPLE_NAME = new Function() + { + @Override + public String apply(String qname) + { + return Iterables.getLast(Splitter.on(".").omitEmptyStrings().split(qname)); + } + }; + + /** + * Get a set of {@link ICSSRule}'s that match any of the class definitions + * passed in. + * + * @param session CSS compilation session data. + * @param flexProject Flex project. + * @param cssDocument CSS document. + * @param qnames A set of QNames of the definitions to be matched the CSS + * rules. + * @param problems Problems collection. + * @return A set of CSS rules matched by one of the given class definitions. + */ + public static ImmutableSet getMatchedRules( + final CSSCompilationSession session, + final IFlexProject flexProject, + final ICSSDocument cssDocument, + final ImmutableSet qnames, + final Collection problems) + { + final boolean isFlex3CSS = flexProject.getCSSManager().isFlex3CSS(); + final ImmutableMap resolvedSelectors = + resolveSelectors(flexProject, cssDocument, problems, isFlex3CSS); + final Predicate predicate; + if (isFlex3CSS) + { + final ImmutableSet simpleNames = + ImmutableSet.copyOf(transform(qnames, QNAME_TO_SIMPLE_NAME)); + predicate = new Flex3CSSRulePredicate(simpleNames); + } + else + { + predicate = new MatchedCSSRulePredicate(qnames, resolvedSelectors); + } + + // Cache the result of selector resolution on the session. + // The CSS code generation will use this map later. + session.resolvedSelectors.putAll(resolvedSelectors); + + // Find rules with selectors that match types in a given definition set. + return ImmutableSet.copyOf(filter(cssDocument.getRules(), predicate)); + } + + /** + * Check if the selector is a wildcard selector. For example: + *
    + *
  • global
  • + *
  • *
  • + *
  • .highlight
  • + *
  • :up
  • + *
+ * + * @param selector CSS selector + * @return True if the selector is a "wildcard" selector. + */ + public static boolean isWildcardSelector(ICSSSelector selector) + { + final String elementName = selector.getElementName(); + return elementName == null || + UNIVERSAL_SELECTOR.equals(elementName) || + GLOBAL_SELECTOR.equals(elementName); + } + + /** + * Build a map from QNames to class definitions. + * + * @param classDefinitions Class definitions. + * @return Lookup map. + */ + public static final ImmutableMap buildQNameToDefinitionMap(final Collection classDefinitions) + { + final Map builder = new HashMap(); + for (final IClassDefinition classDefinition : classDefinitions) + { + builder.put(classDefinition.getQualifiedName(), classDefinition); + } + return ImmutableMap.copyOf(builder); + } + + /** + * Find all the class definitions in the given collection. + * + * @param definitions A collection of definitions. + * @return A set of class definitions. + */ + public static ImmutableSet getClassDefinitionSet(final Collection definitions) + { + final ImmutableSet.Builder builder = new ImmutableSet.Builder(); + for (final IDefinition def : definitions) + { + if (def instanceof IClassDefinition) + builder.add((IClassDefinition)def); + } + final ImmutableSet classDefinitions = builder.build(); + return classDefinitions; + } + + /** + *

+ * Validate a CSS model. The validation only works for Flex 4+. + *

+ *

Find CSS rules with unused type selectors.

+ *

+ * The result is added to the problem collection. + *

+ * For example, if an MXML document only uses {@code } tags, and + * its {@code } block contains: + * + *

+     * ...
+     * s|Button { fontSize : 12; }
+     * local|MyComponent {  color : red; }
+     * ...
+     * 
+ * + * Since {@code } isn't used in the current MXML + * document, the {@code local|MyComponent} is a rule with an unused type + * selector. + *

+ * The validation process only finds all the unused type selectors, but it + * doesn't take them out of the code generation. + *

+ *

Find usages of excludes styles.

If a component declares one of + * its styles to be "excluded", usages of such styles will be reported as + * {@link CSSExcludedStylePropertyProblem}. + * + *
+     * [Exclude(kind="style", name="foo")]
+     * public class MyButton
+     * {
+     * }
+     * 
+ * + * The following CSS will cause the problem: + * + *
+     * local|MyButton { foo : something; }
+     * 
+ * + * @param linkingCompilationUnits All type selectors that doesn't map to any + * definition in this collection are "unused". + * @param session {@link CSSCompilationSession#cssDocuments} has all the CSS + * models an MXML document has. + * @throws InterruptedException Abort compilation. + */ + public static void validate( + final Set linkingCompilationUnits, + final CSSCompilationSession session, + final Collection problems) + throws InterruptedException + { + final CSSValidator validator = new CSSValidator(session, linkingCompilationUnits, problems); + for (final ICSSDocument cssDocument : session.cssDocuments) + { + visit(cssDocument, validator); + } + } + + /** + * CSS model visitor. + */ + private static interface ICSSVisitor + { + + /** + * Visit a CSS document. + */ + void beginDocument(final ICSSDocument document); + + /** + * Visit a CSS rule. + */ + void beginRule(final ICSSRule rule); + + /** + * Visit a CSS subject selector. + */ + void beginSubject(final ICSSSelector selector, final ICSSRule rule); + + /** + * Visit a CSS property. + */ + void beginProperty(final ICSSProperty property, final ICSSRule rule); + } + + /** + * Validate the following CSS semantic constraints: + *
    + *
  1. unused type selectors
  2. + *
  3. usage of excluded styles
  4. + *
+ */ + private static class CSSValidator implements ICSSVisitor + { + private final CSSCompilationSession session; + private final ImmutableMap qnameToDefinition; + private final Collection problems; + private final Multimap excludedStyles; + + /** + * Create a CSS validating visitor. + * + * @param session CSS compilation session. + * @param linkingCompilationUnits All the compilation units to be + * linked. This is used to find unused type selectors. + * @param problems Problem collection. + * @throws InterruptedException Compilation aborted. + */ + private CSSValidator(final CSSCompilationSession session, + final Set linkingCompilationUnits, + final Collection problems) throws InterruptedException + { + this.session = session; + final ImmutableList linkingDefinitions = + Target.getAllExternallyVisibleDefinitions(linkingCompilationUnits); + final ImmutableSet classDefinitions = + getClassDefinitionSet(linkingDefinitions); + this.qnameToDefinition = buildQNameToDefinitionMap(classDefinitions); + this.problems = problems; + this.excludedStyles = HashMultimap.create(); + } + + /** + *
    + *
  1. Find all excluded styles for the current subject.
  2. + *
  3. Find unused type selectors.
  4. + *
+ */ + @Override + public void beginSubject(final ICSSSelector selector, final ICSSRule rule) + { + if (!isWildcardSelector(selector)) + { + final String qname = session.resolvedSelectors.get(selector); + if (qnameToDefinition.containsKey(qname)) + { + // The subject's resolved QName is in the linking set. + // Collect all "excluded" styles for this subject selector. + // Only check "Exclude" styles on used styles. + final IClassDefinition classDefinition = qnameToDefinition.get(qname); + final IMetaTag[] excludeMetaTags = classDefinition.getMetaTagsByName(IASLanguageConstants.EXCLUDE_META_TAG); + for (final IMetaTag exclude : excludeMetaTags) + { + final String kind = exclude.getAttributeValue(IASLanguageConstants.EXCLUDE_META_TAG_KIND); + if (IASLanguageConstants.EXCLUDE_META_TAG_STYLE.equals(kind)) + { + final String excludedStyleName = exclude.getAttributeValue(IASLanguageConstants.EXCLUDE_META_TAG_NAME); + if (excludedStyleName != null && !excludedStyleName.isEmpty()) + { + this.excludedStyles.put(selector, excludedStyleName); + } + } + } + } + else + { + // Selector's resolved QName is not in the linking set. + problems.add(new CSSUnusedTypeSelectorProblem(selector)); + } + } + } + + @Override + public void beginRule(final ICSSRule rule) + { + } + + @Override + public void beginDocument(final ICSSDocument document) + { + } + + /** + * Check usages of excluded styles. + */ + @Override + public void beginProperty(final ICSSProperty property, final ICSSRule rule) + { + for (final ICSSSelector subject : rule.getSelectorGroup()) + { + final Collection excludedStylesForSubject = excludedStyles.get(subject); + if (excludedStylesForSubject != null && excludedStylesForSubject.contains(property.getName())) + { + final String qname = session.resolvedSelectors.get(subject); + problems.add(new CSSExcludedStylePropertyProblem(property, qname)); + } + + } + } + + } + + /** + * Visit a CSS document model. + * + * @param document CSS model. + * @param visitor Handler for various visit methods. + */ + private static void visit(final ICSSDocument document, final ICSSVisitor visitor) + { + visitor.beginDocument(document); + for (final ICSSRule rule : document.getRules()) + { + visitor.beginRule(rule); + for (final ICSSSelector selector : rule.getSelectorGroup()) + visitor.beginSubject(selector, rule); + + for (final ICSSProperty property : rule.getProperties()) + visitor.beginProperty(property, rule); + } + } + +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/definitions/AccessorDefinition.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/definitions/AccessorDefinition.java b/compiler/src/main/java/org/apache/flex/compiler/internal/definitions/AccessorDefinition.java new file mode 100644 index 0000000..aadd143 --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/definitions/AccessorDefinition.java @@ -0,0 +1,237 @@ +/* + * + * 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.flex.compiler.internal.definitions; + +import java.util.Iterator; + + +import org.apache.flex.compiler.definitions.IAccessorDefinition; +import org.apache.flex.compiler.definitions.IClassDefinition; +import org.apache.flex.compiler.definitions.IDefinition; +import org.apache.flex.compiler.definitions.IFunctionDefinition; +import org.apache.flex.compiler.definitions.IGetterDefinition; +import org.apache.flex.compiler.definitions.IInterfaceDefinition; +import org.apache.flex.compiler.definitions.INamespaceDefinition; +import org.apache.flex.compiler.definitions.IPackageDefinition; +import org.apache.flex.compiler.definitions.ISetterDefinition; +import org.apache.flex.compiler.projects.ICompilerProject; +import org.apache.flex.compiler.scopes.IDefinitionSet; +import org.apache.flex.compiler.tree.as.IVariableNode; +import org.apache.flex.compiler.definitions.IScopedDefinition; +import org.apache.flex.compiler.definitions.references.INamespaceReference; +import org.apache.flex.compiler.internal.as.codegen.BindableHelper; +import org.apache.flex.compiler.internal.scopes.ASScope; + +/** + * {@code AccessorDefinition} is the abstract base class for definitions that + * represent getters and setters. + */ +public abstract class AccessorDefinition extends FunctionDefinition implements IAccessorDefinition +{ + public AccessorDefinition(String name) + { + super(name); + } + + @Override + public AccessorDefinition resolveCorrespondingAccessor(ICompilerProject project) + { + IDefinition parent = getParent(); + + if (parent instanceof IClassDefinition) + { + // This accessor is in a class, so look for a corresponding one + // in this class and then in all superclasses. + Iterator iter = ((IClassDefinition)parent).classIterator(project, true); + while (iter.hasNext()) + { + IClassDefinition cls = iter.next(); + + AccessorDefinition correspondingAccessor = + findCorrespondingAccessor(cls, project); + + if (correspondingAccessor != null) + return correspondingAccessor; + } + } + else if (parent instanceof IInterfaceDefinition) + { + // This accessor is in an interface, so look for a corresponding one + // in this interface and then in all superinterfaces. + Iterator iter = ((IInterfaceDefinition)parent).interfaceIterator(project, true); + while (iter.hasNext()) + { + IInterfaceDefinition intf = iter.next(); + + AccessorDefinition correspondingAccessor = + findCorrespondingAccessor(intf, project); + + if (correspondingAccessor != null) + return correspondingAccessor; + } + } + else if (parent instanceof IPackageDefinition) + { + IPackageDefinition pd = (IPackageDefinition)parent; + return findCorrespondingAccessor(pd, project); + } + else if (parent == null) + { + // if the parent definition is null, we must be at file scope, so must search the scope + // directly + ASScope scope = this.getContainingASScope(); + return findCorrespondingAccessor(scope, project); + } + else + assert false; // we should have code for all cases... + + return null; + } + + /** + * Looks in a specified class or interface for an accessor that corresponds + * to "this". i.e. if "this" is a getter, find the matching setter. + * + * @param type is the definition to search for corresponding def + * @return an accessor definition that matches, or null if none found + */ + + private AccessorDefinition findCorrespondingAccessor(IScopedDefinition type, ICompilerProject project) + { + final ASScope scope = (ASScope)type.getContainedScope(); + return findCorrespondingAccessor(scope, project); + } + + /** + * Looks in a specified scope for an accessor that corresponds to "this". + * i.e. if "this" is a getter, find the matching setter. + * + * @param scope is the scope to search for corresponding def + * @return an accessor definition that matches, or null if none found + */ + + private AccessorDefinition findCorrespondingAccessor(ASScope scope, ICompilerProject project) + { + final String name = getBaseName(); + final INamespaceReference namespaceReference = getNamespaceReference(); + final boolean isStatic = isStatic(); + + // If the namespace is bad and dosn't resolve, then we can't find corresponding accessor. + final INamespaceDefinition thisNamespaceDef = namespaceReference.resolveNamespaceReference(project); + if (thisNamespaceDef == null) + return null; + final boolean isBindable = ((NamespaceDefinition)thisNamespaceDef).getAETNamespace().getName().equals( + BindableHelper.bindableNamespaceDefinition.getAETNamespace().getName()); + + final IDefinitionSet definitionSet = scope.getLocalDefinitionSetByName(name); + + if (definitionSet == null) + return null; + + final int n = definitionSet.getSize(); + for (int i = 0; i < n; i++) + { + IDefinition d = definitionSet.getDefinition(i); + if (d instanceof IAccessorDefinition) + { + final IAccessorDefinition definition = (IAccessorDefinition)d; + + // If this is a static accessor, we want another static accessor. + // If this is an instance accessor, we want another instance accessor. + if (definition.isStatic() == isStatic) + { + // If this is a getter, we want a setter, and vice versa. + if (this instanceof IGetterDefinition && definition instanceof ISetterDefinition || + this instanceof ISetterDefinition && definition instanceof IGetterDefinition) + { + INamespaceReference testDefRef = definition.getNamespaceReference(); + INamespaceDefinition testNamespaceDef = testDefRef.resolveNamespaceReference(project); + final boolean testBindable = ((NamespaceDefinition)testNamespaceDef).getAETNamespace().getName().equals( + BindableHelper.bindableNamespaceDefinition.getAETNamespace().getName()); + /* aharui: namespaces shouldn't have to match. A subclass may only override + * one of the protected methods, and it was legal to have a public getter with + * a protected setter and other combinations like that. Either both + * have to be in the bindable namespace, or both are not. */ + if ((isBindable && testBindable) || + (!isBindable && !testBindable)) + return (AccessorDefinition)definition; + } + } + } + } + + return null; + } + + /** + * Get the classification for this variable (local, argument, class member, + * etc) + * + * @return variable classification + */ + @Override + public VariableClassification getVariableClassification() + { + IDefinition parent = getParent(); + + if (parent instanceof IFunctionDefinition) + return VariableClassification.LOCAL; + if (parent instanceof IClassDefinition) + return VariableClassification.CLASS_MEMBER; + if (parent instanceof IInterfaceDefinition) + return VariableClassification.INTERFACE_MEMBER; + if (parent instanceof IPackageDefinition) + return VariableClassification.PACKAGE_MEMBER; + if (parent == null) + { + if (inPackageNamespace()) + return VariableClassification.PACKAGE_MEMBER; + + return VariableClassification.FILE_MEMBER; + } + + assert false; + return null; + } + + @Override + public IVariableNode getVariableNode() + { + return (IVariableNode)super.getNode(); + } + + @Override + public Object resolveInitialValue(ICompilerProject project) + { + return null; + } + + @Override + public boolean inlineFunction() + { + // if inlining has been enabled, don't need to check + // for inline keyword, as inline all getters/setters + // as long as they meet the correct criteria. + if (canFunctionBeInlined()) + return true; + + return false; + } +}