| /* |
| * |
| * 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 flex2.compiler.css; |
| |
| import flash.css.Descriptor; |
| import flash.css.MediaList; |
| import flash.css.StyleDeclaration; |
| import flash.css.StyleDeclarationBlock; |
| import flash.css.StyleProperty; |
| import flash.css.StyleSelector; |
| import flex2.compiler.Source; |
| import flex2.compiler.mxml.MxmlConfiguration; |
| import flex2.compiler.mxml.lang.FrameworkDefs; |
| import flex2.compiler.mxml.reflect.Type; |
| import flex2.compiler.mxml.rep.AtEmbed; |
| import flex2.compiler.mxml.rep.MxmlDocument; |
| import flex2.compiler.util.CompilerMessage.CompilerError; |
| import flex2.compiler.util.ThreadLocalToolkit; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Map.Entry; |
| |
| import macromedia.asc.util.ContextStatics; |
| |
| import org.w3c.css.sac.LexicalUnit; |
| |
| /** |
| * A helper class to aggregate style declarations for a particular subject |
| * (essentially a "type" or in Flex's case, a "component", or the special case |
| * of 'global' which applies to all components). |
| * |
| * From Flex 4 onwards, advanced selector support was added which changes the |
| * focus of this class and it must now consider multiple style declarations |
| * for a given subject. In Flex 3 the style declaration would only apply to a |
| * single type: |
| * |
| * Button { color: #FF0000; } |
| * |
| * or a universal class selector: |
| * |
| * .special { color:#FF9900; } |
| * |
| * In Flex 4, real class selectors are supported on a type-by-type basis and |
| * many selectors can target a type: |
| * |
| * Button { color: #FF0000; } |
| * Button.special { color: #0000FF; } |
| * VBox Panel Button { color: #FF0000; } |
| * Button#clickButton { color: #CCCCCC; } |
| * |
| * These declarations are used to emit an ActionScript class for the subject |
| * which is linked into the top level application as a mix-in as needed. |
| * |
| * This class is complicated by the fact that it supports both Flex 3 and |
| * Flex 4 style subsystems independently. |
| * |
| * @author Paul Reilly |
| * @author Pete Farland |
| */ |
| public class StyleDef |
| { |
| private String subject; |
| private List<String> effectStyles = new ArrayList<String>(); |
| |
| // Flex 4 properties |
| private Map<String, StyleDeclaration> declarations; |
| private boolean advanced; |
| |
| // Deprecated Flex 3 properties |
| private boolean isTypeSelector; |
| private Map<String, StyleProperty> styles = new HashMap<String, StyleProperty>(); |
| |
| private MxmlDocument mxmlDocument; |
| private MxmlConfiguration mxmlConfiguration; // may be null if passed in from a StyleModule |
| private Source source; |
| private int lineNumber; |
| private ContextStatics perCompileData; |
| |
| private static final String CLASS_REFERENCE = "ClassReference("; |
| private static final String EMBED = "Embed("; |
| public static final String GLOBAL = "global"; // A special Flex root selector |
| public static final String UNIVERSAL = "*"; |
| private static final String PROPERTY_REFERENCE = "PropertyReference("; |
| |
| // Flex 4 Constructor |
| StyleDef(String subject, |
| MxmlDocument mxmlDocument, |
| MxmlConfiguration mxmlConfiguration, |
| Source source, |
| int lineNumber, |
| ContextStatics perCompileData) |
| { |
| this.subject = subject; |
| this.mxmlDocument = mxmlDocument; |
| this.mxmlConfiguration = mxmlConfiguration; |
| this.source = source; |
| this.lineNumber = lineNumber; |
| this.perCompileData = perCompileData; |
| advanced = true; |
| isTypeSelector = !GLOBAL.equals(subject) && !UNIVERSAL.equals(subject); |
| } |
| |
| // Deprecated Flex 3 Constructor |
| StyleDef(String name, |
| boolean isTypeSelector, |
| MxmlDocument mxmlDocument, |
| MxmlConfiguration mxmlConfiguration, |
| Source source, |
| int lineNumber, |
| ContextStatics perCompileData) |
| { |
| this.subject = name; |
| this.isTypeSelector = isTypeSelector; |
| this.mxmlDocument = mxmlDocument; |
| this.mxmlConfiguration = mxmlConfiguration; |
| this.source = source; |
| this.lineNumber = lineNumber; |
| this.perCompileData = perCompileData; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Flex 4 Entry Point |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Flex 4 method of declaring styles using advanced selectors, including |
| * type selectors, conditional class, id or pseudo-class (states) selectors, |
| * and descendant selectors. |
| * |
| * This method assumes the selector applies to the subject of this StyleDef. |
| * |
| * @param declaration The style declaration for this rule (which contains |
| * the list of property descriptors). |
| */ |
| void addAdvancedDeclaration(StyleDeclaration declaration, MediaList mediaList) |
| { |
| if (declarations == null) |
| declarations = new LinkedHashMap<String, StyleDeclaration>(); |
| |
| StyleSelector selector = declaration.getSelector(); |
| String selectorString = selector.toString(); |
| StyleDeclaration existingDeclaration = declarations.get(selectorString); |
| // This may be the first time we've seen this declaration |
| if (existingDeclaration == null) |
| { |
| existingDeclaration = declaration; |
| declarations.put(selectorString, existingDeclaration); |
| } |
| |
| // TODO: We should resolve the differences between Descriptor and |
| // StyleProperty and just merge them into the same entity. Special |
| // processing could be done on those descriptors directly. |
| |
| // Extract the properties from the declaration and record them in a |
| // style declaration block. We merge properties from new declaration |
| // blocks to existing declaration blocks because selectors can be |
| // declared multiple times with different properties. This is primarily |
| // done to avoid redundant ClassReferences when it can be determined at |
| // compile time that only the last occurrence of a selector with the |
| // same property will matter at runtime (because the last one wins). |
| // This helps keep SWF size down by avoiding linking in unused classes. |
| StyleDeclarationBlock block = existingDeclaration.getDeclarationBlock(selector, mediaList); |
| extractProperties(declaration, block.getProperties(), block.getEffectStyles()); |
| |
| // If the new declaration does not have a media list, we merge its |
| // properties into all other existing declaration blocks for this |
| // selector that have any of the same properties. This is necessary to |
| // ensure that later declaration blocks clobber all earlier declaration |
| // blocks. |
| if (mediaList == null) |
| { |
| Map<String, StyleProperty> properties = block.getProperties(); |
| |
| List<StyleDeclarationBlock> existingBlocks = existingDeclaration.getDeclarationBlocks(); |
| for (StyleDeclarationBlock existingBlock : existingBlocks) |
| { |
| if (existingBlock != block) |
| { |
| Map<String, StyleProperty> existingProperties = existingBlock.getProperties(); |
| for (String property : properties.keySet()) |
| { |
| if (existingProperties.get(property) != null) |
| existingProperties.put(property, properties.get(property)); |
| } |
| } |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Deprecated Flex 3 Entry Point |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Deprecated Flex 3 method of declaring styles for a single type or |
| * universal class selector. |
| */ |
| void addDeclaration(StyleDeclaration declaration) |
| { |
| extractProperties(declaration, styles, effectStyles); |
| } |
| |
| /** |
| * Determines whether this definition expects to generate Flex 4 styled |
| * Advanced StyleDeclarations. |
| * @return true if advanced style declarations should be generated, |
| * otherwise false. |
| */ |
| public boolean isAdvanced() |
| { |
| return advanced; |
| } |
| |
| public void setAdvanced(boolean value) |
| { |
| advanced = value; |
| } |
| |
| /** |
| * |
| * @return true if a style manager will store style declarations that are |
| * the same as its parent style manager. |
| */ |
| public boolean getAllowDuplicateDefaultStyleDeclarations() |
| { |
| if (mxmlConfiguration != null) |
| return mxmlConfiguration.getAllowDuplicateDefaultStyleDeclarations(); |
| |
| return false; // default value |
| } |
| |
| public Set<AtEmbed> getAtEmbeds() |
| { |
| Set<AtEmbed> result = new HashSet<AtEmbed>(); |
| |
| if (advanced) |
| { |
| if (declarations != null) |
| { |
| for (StyleDeclaration styleDeclaration : declarations.values()) |
| { |
| Collection<StyleDeclarationBlock> declarationBlocks = styleDeclaration.getDeclarationBlocks(); |
| for (StyleDeclarationBlock block : declarationBlocks) |
| { |
| Map<String, StyleProperty> properties = block.getProperties(); |
| |
| if (properties != null) |
| { |
| extractAtEmbeds(result, properties.values()); |
| } |
| } |
| } |
| } |
| } |
| else if (styles != null) |
| { |
| extractAtEmbeds(result, styles.values()); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Extracts the AtEmbeds from the <code>styleProperties</code> and |
| * puts them into the <code>atEmbeds</code>. |
| */ |
| private static void extractAtEmbeds(Set<AtEmbed> atEmbeds, |
| Collection<StyleProperty> styleProperties) |
| { |
| for (StyleProperty styleProperty : styleProperties) |
| { |
| Object value = styleProperty.getValue(); |
| |
| if (value instanceof AtEmbed) |
| { |
| atEmbeds.add((AtEmbed) value); |
| } |
| } |
| } |
| |
| // Flex 4 Method |
| /** |
| * List of all of the style declarations for the subject represented by |
| * this StyleDef. |
| */ |
| public Map<String, StyleDeclaration> getDeclarations() |
| { |
| return declarations; |
| } |
| |
| public List<String> getEffectStyles() |
| { |
| return effectStyles; |
| } |
| |
| public Set<Import> getImports() |
| { |
| Set<Import> result = new HashSet<Import>(); |
| |
| if (advanced) |
| { |
| if (declarations != null) |
| { |
| for (StyleDeclaration styleDeclaration : declarations.values()) |
| { |
| Collection<StyleDeclarationBlock> declarationBlocks = styleDeclaration.getDeclarationBlocks(); |
| for (StyleDeclarationBlock block : declarationBlocks) |
| { |
| Map<String, StyleProperty> properties = block.getProperties(); |
| |
| if (properties != null) |
| { |
| extractImports(result, properties.values()); |
| } |
| } |
| } |
| } |
| } |
| else if (styles != null) |
| { |
| extractImports(result, styles.values()); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Extracts the imports from the <code>styleProperties</code> and |
| * puts them into the <code>imports</code>. |
| */ |
| private static void extractImports(Set<Import> imports, |
| Collection<StyleProperty> styleProperties) |
| { |
| for (StyleProperty styleProperty : styleProperties) |
| { |
| Object value = styleProperty.getValue(); |
| |
| if (value instanceof Reference) |
| { |
| Reference reference = (Reference) value; |
| |
| if (reference.isClassReference() && !reference.getValue().equals("null")) |
| { |
| imports.add(new Import(reference.getValue(), reference.getLineNumber())); |
| } |
| } |
| } |
| } |
| |
| // Deprecated Flex 3 Method |
| public int getLineNumber() |
| { |
| return lineNumber; |
| } |
| |
| // Deprecated Flex 3 Method |
| /** |
| * Flex 3 only supported a single "style declaration" per type or universal |
| * class selector, so this |
| */ |
| public Map<String, StyleProperty> getStyles() |
| { |
| return styles; |
| } |
| |
| /** |
| * The subject is defined as the right most simple type selector in a |
| * potential chain of selectors. A StyleDef contains all of the style |
| * declarations that have selectors with this subject. |
| * |
| * @return the subject name |
| */ |
| public String getSubject() |
| { |
| return subject; |
| } |
| |
| /** |
| * The typeName is used to construct the class name of the ActionScript |
| * mix-in that represents this subject. Essentially it is the subject |
| * name. |
| */ |
| public String getTypeName() |
| { |
| if (UNIVERSAL.equals(subject)) |
| return GLOBAL; |
| else |
| return createTypeName(subject); |
| } |
| |
| // Deprecated Flex 3 Method |
| public boolean isTypeSelector() |
| { |
| return isTypeSelector; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Helper Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Converts a subject into a safe type name so that it can be used in |
| * a generated ActionScript class. |
| */ |
| public static String createTypeName(String subject) |
| { |
| subject = subject.replace('.', '_'); |
| return subject; |
| } |
| |
| /** |
| * This method is useful for converting CSS style declaration |
| * names, like font-size, into valid ActionScript identifiers, |
| * like fontSize. |
| */ |
| public static String dehyphenize(String string) |
| { |
| StringBuilder stringBuffer = new StringBuilder(); |
| |
| int start = 0; |
| int end = string.indexOf('-'); |
| |
| while (end >= 0) |
| { |
| stringBuffer.append( string.substring(start, end) ); |
| stringBuffer.append( Character.toUpperCase( string.charAt(end + 1) ) ); |
| start = end + 2; |
| end = string.indexOf('-', start); |
| } |
| |
| stringBuffer.append( string.substring(start) ); |
| |
| return stringBuffer.toString(); |
| } |
| |
| /** |
| * Convert SAC based CSS parser "Descriptors" into simple |
| * StyleProperties while looking for special Flex syntax such as |
| * Embed(), ClassReference(), and PropertyReference(). |
| */ |
| private void extractProperties(StyleDeclaration declaration, Map<String, StyleProperty> properties, List<String> effectsStyles) |
| { |
| Iterator<Entry<String, Descriptor>> propertyIterator = declaration.iterator(); |
| |
| while (propertyIterator.hasNext()) |
| { |
| Entry<String, Descriptor> entry = propertyIterator.next(); |
| Descriptor descriptor = entry.getValue(); |
| String propertyName = dehyphenize(descriptor.getName()); |
| |
| try |
| { |
| if (propertyName.equals("fontFamily")) |
| { |
| processFontFamily(properties, descriptor); |
| } |
| else |
| { |
| processPropertyDescriptor(propertyName, descriptor, properties, effectsStyles); |
| } |
| } |
| catch (CompilerError compilerError) |
| { |
| compilerError.setPath(descriptor.getPath()); |
| compilerError.setLine(descriptor.getLineNumber()); |
| ThreadLocalToolkit.log(compilerError); |
| } |
| } |
| } |
| |
| /** |
| * Process ClassReference and PropertyReference CSS functions. |
| */ |
| private Reference processReference(String value, String styleSheetPath, |
| int line, boolean isClassReference) |
| { |
| Reference result = null; |
| int prefixLength; |
| |
| if (isClassReference) |
| { |
| prefixLength = CLASS_REFERENCE.length(); |
| } |
| else |
| { |
| prefixLength = PROPERTY_REFERENCE.length(); |
| } |
| |
| String parameter = value.substring(prefixLength, value.length() - 1).trim(); |
| |
| if ((parameter.charAt(0) == '"') && (parameter.indexOf('"', 1) == parameter.length() - 1)) |
| { |
| String substring = parameter.substring(1, parameter.length() - 1); |
| |
| if (!isClassReference && (mxmlDocument == null)) |
| { |
| PropertyReferenceRequiresDocument propertyReferenceRequiresDocument = |
| new PropertyReferenceRequiresDocument(); |
| propertyReferenceRequiresDocument.path = styleSheetPath; |
| propertyReferenceRequiresDocument.line = line; |
| ThreadLocalToolkit.log(propertyReferenceRequiresDocument); |
| } |
| else |
| { |
| result = new Reference(substring, isClassReference, styleSheetPath, line); |
| } |
| } |
| else if (parameter.equals("null")) |
| { |
| result = new Reference(parameter, isClassReference, styleSheetPath, line); |
| } |
| else |
| { |
| InvalidReference invalidReference = new InvalidReference(isClassReference); |
| invalidReference.path = styleSheetPath; |
| invalidReference.line = line; |
| ThreadLocalToolkit.log(invalidReference); |
| } |
| |
| return result; |
| } |
| |
| private void processFontFamily(Map<String, StyleProperty> properties, Descriptor descriptor) |
| { |
| String propertyName = "fontFamily"; |
| String fontFamily = descriptor.getIdentAsString(); |
| StyleProperty stylesProperty = new StyleProperty(propertyName, |
| "\"" + fontFamily + "\"", |
| descriptor.getPath(), |
| descriptor.getLineNumber()); |
| properties.put(propertyName, stylesProperty); |
| } |
| |
| private void processPropertyDescriptor(String propertyName, Descriptor descriptor, |
| Map<String, StyleProperty> properties, |
| List<String> effectStyles) |
| throws CompilerError |
| { |
| if (propertyName.endsWith("Effect")) |
| { |
| effectStyles.add(propertyName); |
| } |
| |
| Object value = processPropertyValue(descriptor); |
| |
| if (value != null) |
| { |
| StyleProperty styleProperty = new StyleProperty(propertyName, value, |
| descriptor.getPath(), |
| descriptor.getLineNumber()); |
| properties.put(propertyName, styleProperty); |
| } |
| } |
| |
| private Object processPropertyValue(Descriptor descriptor) |
| throws CompilerError |
| { |
| String value = descriptor.getValueAsString(); |
| Object result = value; |
| |
| if (value.startsWith(EMBED)) |
| { |
| result = AtEmbed.create(perCompileData, source, value, |
| descriptor.getPath(), descriptor.getLineNumber(), |
| "_embed_css_"); |
| } |
| else if (value.startsWith(CLASS_REFERENCE)) |
| { |
| result = processReference(value, descriptor.getPath(), descriptor.getLineNumber(), true); |
| } |
| else if (value.startsWith(PROPERTY_REFERENCE)) |
| { |
| result = processReference(value, descriptor.getPath(), descriptor.getLineNumber(), false); |
| } |
| |
| // Strip the quotes for CSS identifiers, which are properties |
| // of the Mxml document. This allows us to support minimal |
| // data binding functionality for the CSS object styling feature. |
| // |
| // The "advanced" check is really a check for compatibility |
| // version greater than 3.0. If the way advanced gets set |
| // changes, this should be updated. |
| // |
| if (!advanced && |
| (mxmlDocument != null) && |
| (descriptor.getValue().getLexicalUnitType() == LexicalUnit.SAC_IDENT) && |
| (value != null) && |
| (value.length() > 2) && |
| ((value.charAt(0) == '\'') || (value.charAt(0) == '\"')) && |
| ((value.charAt(value.length() - 1) == '\'') || (value.charAt(value.length() - 1) == '\"'))) |
| { |
| String potentialProperty = value.substring(1, value.length() - 1); |
| |
| Type type = mxmlDocument.getRoot().getType(); |
| |
| if (type != null && type.getProperty(potentialProperty) != null) |
| { |
| result = potentialProperty; |
| } |
| |
| // deprecated - Flex 1.5 support only |
| if ( FrameworkDefs.isBuiltinEffectName(potentialProperty) ) |
| { |
| mxmlDocument.addTypeRef(mxmlDocument.getStandardDefs().getEffectsPackage() + '.' + potentialProperty, |
| descriptor.getLineNumber()); |
| } |
| } |
| |
| return result; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Errors |
| // |
| //-------------------------------------------------------------------------- |
| |
| public static class InvalidReference extends CompilerError |
| { |
| private static final long serialVersionUID = 3730898410175891394L; |
| public String type; |
| |
| public InvalidReference(boolean isClassReference) |
| { |
| if (isClassReference) |
| { |
| type = "Class"; |
| } |
| else |
| { |
| type = "Property"; |
| } |
| } |
| } |
| |
| public static class PropertyReferenceRequiresDocument extends CompilerError |
| { |
| private static final long serialVersionUID = 3730898410175891396L; |
| |
| public PropertyReferenceRequiresDocument() |
| { |
| } |
| } |
| } |