package ca.concordia.cssanalyser.cssmodel.declaration; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import ca.concordia.cssanalyser.csshelper.CSSPropertyCategoryHelper; import ca.concordia.cssanalyser.csshelper.CSSPropertyCategory; import ca.concordia.cssanalyser.cssmodel.CSSModelObject; import ca.concordia.cssanalyser.cssmodel.CSSOrigin; import ca.concordia.cssanalyser.cssmodel.CSSSource; import ca.concordia.cssanalyser.cssmodel.LocationInfo; import ca.concordia.cssanalyser.cssmodel.declaration.value.DeclarationValue; import ca.concordia.cssanalyser.cssmodel.selectors.Selector; /** * The representation of a single CSS declaration which consists of a * a property (as a String). * Values will be there in the subclasses, depending on the number of values (as {@link DeclarationValue}s. * * @author Davood Mazinanian * */ public abstract class Declaration extends CSSModelObject implements Cloneable { protected final String property; protected Selector parentSelector; protected boolean isImportant; protected CSSOrigin origin = CSSOrigin.AUTHOR; protected CSSSource source = CSSSource.EXTERNAL; protected final CSSPropertyCategory propertyCategory; /** * Declarations can be the building blocks of a shorthand declaration. * (for instance, margin-left for a margin). * In this case, this reference will keep track of that. */ protected ShorthandDeclaration parentShorthand; public Declaration(String propertyName, Selector belongsTo, boolean important, LocationInfo location) { property = propertyName.toLowerCase().trim(); parentSelector = belongsTo; isImportant = important; locationInfo = location; propertyCategory = CSSPropertyCategoryHelper.getCSSCategoryOfProperty(getNonVendorProperty(getNonHackedProperty(property))); } /** * For properties which have vendor prefixes * (like -moz-, -webkit-, etc.) * return the property without prefix * @return */ public static String getNonVendorProperty(String property) { String torReturn = property; Set<String> prefixes = new HashSet<>(); prefixes.add("-webkit-"); prefixes.add("-moz-"); prefixes.add("-ms-"); prefixes.add("-o-"); prefixes.add("-ah-"); prefixes.add("-apple-"); prefixes.add("-atsc-"); prefixes.add("-epub-"); prefixes.add("-hp-"); prefixes.add("-khtml-"); prefixes.add("-rim-"); prefixes.add("-ro-"); prefixes.add("-tc-"); prefixes.add("-wap-"); prefixes.add("-xv-"); prefixes.add("-moz-osx-"); for (String prefix : prefixes) if (torReturn.startsWith(prefix)) { torReturn = torReturn.substring(prefix.length()); break; } return torReturn; } public static String getNonHackedProperty(String property) { String torReturn = property; Set<String> prefixes = new HashSet<>(); prefixes.add("*"); prefixes.add("_"); for (String prefix : prefixes) if (torReturn.startsWith(prefix)) { torReturn = torReturn.substring(prefix.length()); break; } return torReturn; } public static String getVendorPrefixForProperty(String property) { String nonVendorproperty = getNonVendorProperty(property); String prefix = ""; if (!property.equals(nonVendorproperty)) prefix = property.substring(0, property.indexOf(nonVendorproperty)); return prefix; } public static boolean canHaveVendorPrefixedProperty(String property) { Set<String> havingVendorPrefixedProperty = new HashSet<>(Arrays.asList("align-content", "align-items", "align-self", "animation", "animation-delay", "animation-duration", "animation-fill-mode", "animation-iteration-count", "animation-name", "appearance", "backface-visibility", "background-clip", "background-size", "border-bottom-colors", "border-bottom-left-radius", "border-bottom-right-radius", "border-left-colors", "border-radius-bottom-left", "border-radius-bottom-right", "border-radius-top-left", "border-radius-top-right", "border-right-colors", "border-top-colors", "border-top-left-radius", "border-top-right-radius", "box-shadow", "box-sizing", "column-count", "filter", "flex", "flex-basis", "flex-direction", "flex-pack", "flex-shrink", "flex-wrap", "font-feature-settings", "font-smoothing", "hyphens", "interpolation-mode", "justify-content", "opacity", "overflow-scrolling", "overflow-style", "perspective", "pointer-events", "tab-size", "tap-highlight-color", "text-size-adjust", "touch-callout", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "user-drag", "user-select", "writing-mode")); return havingVendorPrefixedProperty.contains(getNonHackedProperty(getNonVendorProperty(property))); } /** * Returns true if the declaration is declared with !important * @return */ public boolean isImportant() { return isImportant; } /** * Sets the !important value * @param isImportant */ public void isImportant(boolean isImportant) { this.isImportant = isImportant; } /** * Returns the selector to which this declaration belongs * @return */ public Selector getSelector() { if (this.parentShorthand != null) return parentShorthand.getSelector(); return parentSelector; } /** * Returns the selector to which this declaration belongs * @return */ public void setSelector(Selector selector) { this.parentSelector = selector; if (this.parentSelector != null) { if (!this.parentSelector.containsDeclaration(this)) this.parentSelector.addDeclaration(this); } } /** * Returns the name of the property of this declaration * @return */ public String getProperty() { return property; } /** * Compares two declarations based only on their values to see if they are Equal. * @param otherDeclaration * @return */ protected abstract boolean valuesEqual(Declaration otherDeclaration); /** * Compares two declarations based only on their values to see if they are Equal. * @param otherDeclaration * @return */ protected abstract boolean valuesEquivalent(Declaration otherDeclaration); /** * Return true if the given declarations is equivalent * with this declaration * @param otherDeclaration * @return */ public boolean declarationIsEquivalent(Declaration otherDeclaration) { return compareDeclarations(otherDeclaration, false, true); } /** * Return true if the given declarations is equivalent * with this declaration, ignoring the vendor-prefix properties * @param otherDeclaration * @return */ public boolean declarationIsEquivalent(Declaration otherDeclaration, boolean nonVendorPrefixesEquivalent) { return compareDeclarations(otherDeclaration, nonVendorPrefixesEquivalent, true); } /** * Return true if the given declarations is equal * with this declaration * @param otherDeclaration * @return */ public boolean declarationEquals(Declaration otherDeclaration) { return compareDeclarations(otherDeclaration, false, false); } /** * Compares two declarations based on the given parameters. * @param otherDeclaration * @param skipVendor Skips the vendor-specific prefix * @param equivalent * @return */ private boolean compareDeclarations(Declaration otherDeclaration, boolean skipVendor, boolean equivalent) { String p1 = property; String p2 = otherDeclaration.property; if (skipVendor) { p1 = getNonVendorProperty(p1); p2 = getNonVendorProperty(p2); } if (otherDeclaration.isImportant() != isImportant) return false; if (!p1.equals(p2)) return false; if (parentSelector != null && otherDeclaration.parentSelector != null) { if (parentSelector.getMediaQueryLists() == null) { if (otherDeclaration.parentSelector.getMediaQueryLists() != null) { if (otherDeclaration.parentSelector.getMediaQueryLists().size() != 0) return false; } } else { if (!parentSelector.mediaQueryListsEqual(otherDeclaration.parentSelector)) return false; } } // Template method design pattern if (equivalent) return valuesEquivalent(otherDeclaration); else return valuesEqual(otherDeclaration); } public LocationInfo getLocationInfo() { return this.locationInfo; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (isImportant ? 1231 : 1237); result = prime * locationInfo.hashCode(); result = prime * result + ((origin == null) ? 0 : origin.hashCode()); result = prime * result + ((parentSelector == null) ? 0 : parentSelector.hashCode()); result = prime * result + ((property == null) ? 0 : property.hashCode()); result = prime * result + ((source == null) ? 0 : source.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Declaration other = (Declaration) obj; if (isImportant != other.isImportant) return false; if (origin != other.origin) return false; if (parentSelector == null) { if (other.parentSelector != null) return false; } else if (!parentSelector.equals(other.parentSelector)) return false; if (locationInfo == null) { if (other.locationInfo != null) return false; } else if (!locationInfo.equals(other.locationInfo)) return false; if (property == null) { if (other.property != null) return false; } else if (!property.equals(other.property)) return false; if (source != other.source) return false; return true; } public abstract Declaration clone(); public abstract String toString(); /** * Returns a map which maps every style property to a list of declaration values. * In the case of single-valued declarations, it returns a map with one mapping: property -> the only value. * In the case of multi-valued declarations, it maps every style property to a list. * Each member of the list represents one layer (in multi-layered, comma-separated values). * This list will have one item, if the property is not comma-separated. * Each of the items of this list will be a collection of values, corresponding to the * given property name. * for instance, for property <code>font: bold 10pt Tahoma, Arial</code>, * calling this method like <code>getPropertyToValuesMap("font-family")</code> will * return a list with one item, which is a collection containing "Tahoma" and "Arial". * @return */ protected abstract Map<String, ?> getPropertyToValuesMap(); public abstract Collection<String> getStyleProperties(); public int getDeclarationNumber() { if (this.parentSelector == null && isVirtualIndividualDeclarationOfAShorthand()) { return this.parentShorthand.getDeclarationNumber(); } else { return this.parentSelector.getDeclarationNumber(this); } } public abstract Iterable<DeclarationValue> getDeclarationValues(); public abstract int getNumberOfValueLayers(); public abstract Collection<DeclarationValue> getDeclarationValuesForStyleProperty(String styleProperty, int forLayer); public Collection<DeclarationValue> getDeclarationValuesForStyleProperty(PropertyAndLayer propertyAndLayer) { return getDeclarationValuesForStyleProperty(propertyAndLayer.getPropertyName(), propertyAndLayer.getPropertyLayer()); } public abstract Set<PropertyAndLayer> getAllSetPropertyAndLayers(); public boolean isVirtualIndividualDeclarationOfAShorthand() { return parentShorthand != null; } public ShorthandDeclaration getParentShorthand() { return parentShorthand; } public void setParentShorthand(ShorthandDeclaration parentShorthand) { this.parentShorthand = parentShorthand; } public CSSPropertyCategory getPropertyCategory() { return propertyCategory; } }