package ca.concordia.cssanalyser.cssmodel.declaration; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.NotImplementedException; import ca.concordia.cssanalyser.cssmodel.LocationInfo; import ca.concordia.cssanalyser.cssmodel.declaration.value.DeclarationEquivalentValue; import ca.concordia.cssanalyser.cssmodel.declaration.value.DeclarationValue; import ca.concordia.cssanalyser.cssmodel.declaration.value.ValueType; import ca.concordia.cssanalyser.cssmodel.selectors.Selector; /** * Represents multi-valued declarations * @author Davood Mazinanian * */ public class MultiValuedDeclaration extends Declaration { protected final boolean isCommaSeparatedListOfValues; protected final List<DeclarationValue> declarationValues; /** * Each property is mapped to a Set or List of DeclarationValues. * For layered (comma-separated) properties (such as background), * a layer number is also assigned to each property so we can understand the values * belong to the property of which layer. * For non-layered properties, the layer assigned to each property is one. * We use {@link PropertyAndLayer} class for the keys to store the layer with the property name. */ protected final Map<PropertyAndLayer, Collection<DeclarationValue>> stylePropertyToDeclarationValueMap; private static final Set<String> muliValuedProperties = new HashSet<>(); static { initializeMultiValuedProperties(); } public MultiValuedDeclaration(String propertyName, List<DeclarationValue> values, Selector belongsTo, boolean important, boolean addMissingValues, LocationInfo location) { super(propertyName, belongsTo, important, location); this.declarationValues = values; this.stylePropertyToDeclarationValueMap = new HashMap<>(); this.isCommaSeparatedListOfValues = isCommaSeparated(property); if (addMissingValues) addMissingValues(); } private static void initializeMultiValuedProperties() { String[] properties = new String[] { "background-clip", "background-origin", "background-size", "background-position", "background-image", "background-repeat", "background-attachment", "border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius", "transform-origin", "transition-property", "transition-timing-function", "transition-delay", "transform", "transition-duration", "perspective-origin", "border-spacing", "text-shadow", "box-shadow", "content", "font-family", "quotes", "mask-box-image", }; for (String property : properties) muliValuedProperties.add(property); } /** * Gets a property name (as String) and * determines whether the property can have a list of * comma-separated values (like CSS3 background, font, etc.) * @param property * @return */ public static boolean isCommaSeparated(String property) { switch (property) { case "font-family": case "font": // ? case "background": case "background-clip": case "background-origin": case "background-size": case "background-position": case "background-image": case "background-repeat": case "background-attachment": case "box-shadow": case "text-shadow": case "transition": case "transition-delay": case "transition-duration": case "transition-property": case "transition-timing-function": case "overflow-style": case "animation": case "src": // for @font-face return true; } return false; } public static boolean isMultiValuedProperty(String propertyName) { return muliValuedProperties.contains(getNonVendorProperty(getNonHackedProperty(propertyName))); } /** * This method adds missing values to the different properties * which can have more than one value (like background-position). */ protected void addMissingValues() { // Only multi-valued and non-shorthand declarations are handled here. // Shorthands are handled in ShorthandDeclaration class if (declarationValues == null || declarationValues.size() == 0) return; switch (getNonVendorProperty(getNonHackedProperty(property))) { case "background-position": { //http://www.w3.org/TR/css3-background/#the-background-position List<DeclarationValue> allValues = new ArrayList<>(declarationValues); DeclarationValue sentinel = new DeclarationValue(",", ValueType.SEPARATOR); allValues.add(sentinel); List<DeclarationValue> currentLayerValues = new ArrayList<>(); int currentLayerIndex = 0; for (int currentValueIndex = 0; currentValueIndex < allValues.size(); currentValueIndex++) { DeclarationValue currentValue = allValues.get(currentValueIndex); if (currentValue.getType() == ValueType.SEPARATOR && ",".equals(currentValue.getValue())) { currentLayerIndex++; final String BACKGROUND_POSITION_LEFT = "background-position-left"; final String BACKGROUND_POSITION_TOP = "background-position-top"; DeclarationValue firstValue = currentLayerValues.get(0); DeclarationValue secondValue = null; // TODO add four-valued background-position if (currentLayerValues.size() == 1) { if ("inherit".equals(firstValue.getValue())) { break; } else { if (firstValue.isKeyword()) { secondValue = new DeclarationEquivalentValue("center", "50%", ValueType.LENGTH); } else { secondValue = new DeclarationEquivalentValue("50%", "50%", ValueType.LENGTH); } addMissingValue(secondValue, 1); } } else { secondValue = currentLayerValues.get(1); } if ("top".equals(firstValue.getValue()) || "bottom".equals(firstValue.getValue()) || "left".equals(secondValue.getValue()) || "right".equals(secondValue.getValue())) { assignStylePropertyToValue(BACKGROUND_POSITION_TOP, currentLayerIndex, firstValue, false); assignStylePropertyToValue(BACKGROUND_POSITION_LEFT, currentLayerIndex, secondValue, false); } else { assignStylePropertyToValue(BACKGROUND_POSITION_LEFT, currentLayerIndex, firstValue, false); assignStylePropertyToValue(BACKGROUND_POSITION_TOP, currentLayerIndex, secondValue, false); } currentLayerValues.clear(); } else { currentLayerValues.add(currentValue); } } break; } case "background-size": { //http://www.w3.org/TR/css3-background/#the-background-size List<DeclarationValue> allValues = new ArrayList<>(declarationValues); DeclarationValue sentinel = new DeclarationValue(",", ValueType.SEPARATOR); allValues.add(sentinel); List<DeclarationValue> currentLayerValues = new ArrayList<>(); int currentLayerIndex = 0; for (int currentValueIndex = 0; currentValueIndex < allValues.size(); currentValueIndex++) { DeclarationValue currentValue = allValues.get(currentValueIndex); if (currentValue.getType() == ValueType.SEPARATOR && ",".equals(currentValue.getValue())) { currentLayerIndex++; final String WIDTH = "background-size-width"; final String HEIGHT = "background-size-height"; DeclarationValue firstValue = currentLayerValues.get(0); assignStylePropertyToValue(WIDTH, currentLayerIndex, firstValue, false); DeclarationValue secondValue = null; if (currentLayerValues.size() == 1) { String val = firstValue.getValue(); if (!("cover".equals(val) || "contain".equals(val) || "inherit".equals(val) || "auto".equals(val))) { secondValue = new DeclarationValue("auto", ValueType.LENGTH); } else { secondValue = firstValue.clone(); // There is no second value } assignStylePropertyToValue(HEIGHT, currentLayerIndex, secondValue, false); addMissingValue(secondValue, 1); } else { secondValue = currentLayerValues.get(1); assignStylePropertyToValue(HEIGHT, currentLayerIndex, secondValue, false); } currentLayerValues.clear(); } else { currentLayerValues.add(currentValue); } } break; } case "background-clip": case "background-origin": case "background-image": case "background-repeat": case "background-attachment": { int currentLayer = 0; for (DeclarationValue value : declarationValues) { if (value.getType() != ValueType.SEPARATOR) { assignStylePropertyToValue(getNonVendorProperty(getNonHackedProperty(property)), ++currentLayer, value, true); } } break; } case "border-top-left-radius": case "border-top-right-radius": case "border-bottom-right-radius": case "border-bottom-left-radius": { final String HRADIUS = property + "-horizontal"; final String VRADIUS = property + "-vertical"; DeclarationValue firstValue = declarationValues.get(0); DeclarationValue secondValue = null; // http://www.w3.org/TR/css3-background/ if (declarationValues.size() == 1) { secondValue = firstValue.clone(); addMissingValue(secondValue, 1); } else { secondValue = declarationValues.get(1); } assignStylePropertyToValue(HRADIUS, firstValue); assignStylePropertyToValue(VRADIUS, secondValue); break; } case "transform" : { String TRANSFORM = "transform"; for (DeclarationValue value : declarationValues) { if (value.getType() != ValueType.SEPARATOR) assignStylePropertyToValue(TRANSFORM, value, true); } break; } case "transform-origin": { final String XAXIS = "x-axis"; final String YAXIS = "y-axis"; final String ZAXIS = "z-axis"; // http://www.w3.org/TR/css3-transforms if (declarationValues.size() == 1) { addMissingValue(new DeclarationEquivalentValue("center", "50%", ValueType.LENGTH), 1); addMissingValue(new DeclarationEquivalentValue("0", "0px", ValueType.LENGTH), 2); } else if (declarationValues.size() == 2) { addMissingValue(new DeclarationEquivalentValue("0", "0px", ValueType.LENGTH), 2); } assignStylePropertyToValue(XAXIS, declarationValues.get(0)); assignStylePropertyToValue(YAXIS, declarationValues.get(1)); assignStylePropertyToValue(ZAXIS, declarationValues.get(2)); break; } case "perspective-origin": { final String XAXIS = "x-axis"; final String YAXIS = "y-axis"; // http://www.w3.org/TR/css3-transforms/ if (declarationValues.size() == 1) addMissingValue(new DeclarationEquivalentValue("center", "50%", ValueType.LENGTH), 1); assignStylePropertyToValue(XAXIS, declarationValues.get(0)); assignStylePropertyToValue(YAXIS, declarationValues.get(1)); break; } case "border-spacing": { final String HSPACING = "h-spacing"; final String VSPACING = "v-spacing"; if (declarationValues.size() == 1) addMissingValue(declarationValues.get(0).clone(), 1); assignStylePropertyToValue(HSPACING, declarationValues.get(0)); assignStylePropertyToValue(VSPACING, declarationValues.get(1)); break; } case "text-shadow": case "box-shadow": { /* * They are comma separated * http://www.w3.org/TR/css3-background/#box-shadow * http://www.w3.org/TR/2013/CR-css-text-decor-3-20130801/#text-shadow-property * They are the same, except for "inset" keyword and * spread value (fourth numeric value) which are not allowed for text-shadow */ boolean isBoxShadow = "box-shadow".equals(getNonVendorProperty(getNonHackedProperty(property))); List<DeclarationValue> allValues = new ArrayList<>(declarationValues); DeclarationValue sentinel = new DeclarationValue(",", ValueType.SEPARATOR); allValues.add(sentinel); int currentLayerStartIndex = 0; int totalAddedMissingValues = 0; int currentLayerIndex = 0; for (int currentValueIndex = 0; currentValueIndex < allValues.size(); currentValueIndex++) { DeclarationValue currentValue = allValues.get(currentValueIndex); // Find the first separator. Sentinel is used here :) if (currentValue.getType() == ValueType.SEPARATOR && ",".equals(currentValue.getValue())) { currentLayerIndex++; DeclarationValue inset = null, hOffset = null, vOffset = null, blurRadius = null, spreadDistance = null, color = null, slash = currentValue; int numberOfLengths = 0; boolean isNone = false; // Count from current layer's start index to this separator for (int currentLayerValueIndex = currentLayerStartIndex; currentLayerValueIndex < currentValueIndex; currentLayerValueIndex++) { DeclarationValue currentLayerCurrentValue = allValues.get(currentLayerValueIndex); switch (currentLayerCurrentValue.getType()) { case COLOR: color = currentLayerCurrentValue; break; case IDENT: if ("inset".equals(currentLayerCurrentValue.getValue())) inset = currentLayerCurrentValue; else if ("none".equals(currentLayerCurrentValue.getValue())) { hOffset = new DeclarationValue("0", ValueType.LENGTH); declarationValues.set(currentLayerValueIndex, hOffset); isNone = true; } break; case LENGTH: numberOfLengths++; switch (numberOfLengths) { case 1: hOffset = currentLayerCurrentValue; break; case 2: vOffset = currentLayerCurrentValue; break; case 3: blurRadius = currentLayerCurrentValue; break; case 4: spreadDistance = currentLayerCurrentValue; } default: } } int missingValueOffset = totalAddedMissingValues; int vOffsetPosition = 1, blurPosition = 2, distancePosition = 3, colorPosition = 3; if (isBoxShadow) { colorPosition++; if (inset != null) { vOffsetPosition++; colorPosition++; blurPosition++; distancePosition++; } } if (vOffset == null) { vOffset = new DeclarationEquivalentValue("0", "0px", ValueType.LENGTH); addMissingValue(vOffset, currentLayerStartIndex + missingValueOffset + vOffsetPosition); totalAddedMissingValues++; } if (blurRadius == null) { blurRadius = new DeclarationEquivalentValue("0", "0px", ValueType.LENGTH); addMissingValue(blurRadius, currentLayerStartIndex + missingValueOffset + blurPosition); totalAddedMissingValues++; } if (isBoxShadow && spreadDistance == null) { spreadDistance = new DeclarationEquivalentValue("0", "0px", ValueType.LENGTH); addMissingValue(spreadDistance, currentLayerStartIndex + missingValueOffset + distancePosition); totalAddedMissingValues++; } if (color == null) { if (isNone) color = new DeclarationEquivalentValue("transparent", "rgba(0, 0, 0, 0)", ValueType.COLOR); else color = new DeclarationValue("currentColor", ValueType.COLOR); addMissingValue(color, currentLayerStartIndex + missingValueOffset + colorPosition); totalAddedMissingValues++; } currentLayerStartIndex = currentValueIndex + 1; final String COLOR = "color"; final String HOFFSET = "hoffset"; final String VOFFSET = "voffset"; final String BLUR = "blur"; final String SPREAD = "spread"; final String INSET = "inset"; final String SLASH = "slash"; assignStylePropertyToValue(COLOR, currentLayerIndex, color, false); assignStylePropertyToValue(HOFFSET, currentLayerIndex, hOffset, false); assignStylePropertyToValue(VOFFSET, currentLayerIndex, vOffset, false); assignStylePropertyToValue(BLUR, currentLayerIndex, blurRadius, false); if (isBoxShadow) { assignStylePropertyToValue(SPREAD, currentLayerIndex, spreadDistance, false); if (inset != null) assignStylePropertyToValue(INSET, currentLayerIndex, inset, false); } if (currentValueIndex < allValues.size() - 2) { assignStylePropertyToValue(SLASH, currentLayerIndex, slash, false); } } } break; } case "font-family": case "content": { for (DeclarationValue value : declarationValues) { if (value.getType() != ValueType.SEPARATOR) assignStylePropertyToValue(property, 1, value, true); /*else assignStylePropertyToValue(property + "-comma", 1, value, true);*/ } break; } case "transition-property": case "transition-duration": case "transition-timing-function": case "transition-delay": { for (DeclarationValue value : declarationValues) { if (value.getType() != ValueType.SEPARATOR) assignStylePropertyToValue(property, 1, value, false); // else // assignStylePropertyToValue(property + "-comma", 1, value, false); } break; } case "quotes": { /* * http://www.w3schools.com/cssref/pr_gen_quotes.asp * An even number of strings. Each two of them are used for one level */ final String LEFTQ = "leftq"; final String RIGHTQ = "rightq"; if (declarationValues.size() == 1 && "none".equals(declarationValues.get(0).getValue())) { assignStylePropertyToValue(LEFTQ, 1, declarationValues.get(0), true); assignStylePropertyToValue(RIGHTQ, 1, declarationValues.get(0), true); } else if (declarationValues.size() % 2 == 0) { for (int i = 0; i < declarationValues.size(); i += 2) { assignStylePropertyToValue(LEFTQ, (i / 2) + 1, declarationValues.get(i), true); assignStylePropertyToValue(RIGHTQ, (i / 2) + 1, declarationValues.get(i + 1), true); } } else { throw new RuntimeException("'quotes' property should have even number of variables"); } break; } default: throw new NotImplementedException("Multivalued property " + property + " not handled."); } } /** * Maps a styleProperty to a value. * For instance, in <code>margin: 2px</code>, * this function is called so margin-left, -right, -bottom and -top all map to the value of 2px. * If this method is called for the first time, it adds the new value to a Set mapped to the given styleProperty. * If the styleProperty already exists (i.e., the method has already been called), * it adds the new value to the existing underlying collection (either a List or Set, based on the previous call to * this method or its overload). * @param styleProperty * @param value */ protected void assignStylePropertyToValue(String styleProperty, DeclarationValue value) { assignStylePropertyToValue(styleProperty, value, false); } /** * Maps a styleProperty to a value. * For instance, in <code>margin: 2px</code>, * this function is called so margin-left, -right, -bottom and -top all map to the value of 2px. * If this method is called for the first time, it adds the new value to a Set or List mapped to the given styleProperty, * based on the value of orderImportant parameter (List => orderImportant == true, otherwise, Set). * If this method is called not for the first time, it ignores the value for orderImportant and adds it to the * existing underlying collection. * @param styleProperty * @param value * @param orderImportant */ protected void assignStylePropertyToValue(String styleProperty, DeclarationValue value, boolean orderImportant) { assignStylePropertyToValue(styleProperty, 1, value, orderImportant); } protected void assignStylePropertyToValue(String styleProperty, int layer, DeclarationValue value, boolean orderImportant) { value.setCorrespondingStyleProperty(styleProperty, layer); PropertyAndLayer propertyAndLayer = new PropertyAndLayer(styleProperty, layer); Collection<DeclarationValue> values = stylePropertyToDeclarationValueMap.get(propertyAndLayer); if (values == null) { if (orderImportant) values = new ArrayList<>(); else values = new HashSet<>(); } values.add(value); stylePropertyToDeclarationValueMap.put(propertyAndLayer, values); } /** * Adds a missing value (a value which is missing from the * real declaration in the file, but is implied in the W3C * recommendations.) <br /> * Note that it calls {@link DeclarationValue#setIsAMissingValue(true)} * for given declaration. * @param {@link DeclarationValue} to be added * @param position The position in the value list to which * this value should be added (zero based) * @param */ public void addMissingValue(DeclarationValue value, int position) { value.setIsAMissingValue(true); declarationValues.add(position, value); } /** * Returns the number of missing values this declaration have * @return */ public int getNumberOfMissingValues() { return ((List<DeclarationValue>)getMissingValues()).size(); } /** * Returns a list of values for current declaration. * This list also includes the values which are not in the * real file, but are added later (missing values). * @return */ public List<DeclarationValue> getValues() { return declarationValues; } /** * Only returns the real values for this declaration * (not the missing values which were added later). * @return */ public Iterable<DeclarationValue> getRealValues() { List<DeclarationValue> realValues = new ArrayList<>(); for (DeclarationValue value : getValues()) { if (!value.isAMissingValue()) realValues.add(value); } return realValues; } /** * Only returns the missing values for this declaration * which are added later * @return */ public Iterable<DeclarationValue> getMissingValues() { List<DeclarationValue> missingValues = new ArrayList<>(); for (DeclarationValue value : getValues()) { if (value.isAMissingValue()) missingValues.add(value); } return missingValues; } /** * Add a value to this declaration * @param value */ public void addValue(DeclarationValue value) { declarationValues.add(value); } @Override public String toString() { String toReturn = getValuesString(); return String.format("%s: %s", property, toReturn); } /** * Returns the string corresponding to the values * @return */ protected String getValuesString() { StringBuilder valueString = new StringBuilder(""); for (int i = 0; i < declarationValues.size(); i++) { DeclarationValue v = declarationValues.get(i); if (v.isAMissingValue()) continue; boolean addSpace = true; if (addSpace) { // Find the next value which is not missing. If it is a comma, don't add space to get "a, b" style values. int k = i; while (++k < declarationValues.size()) { DeclarationValue tv = declarationValues.get(k); if (!tv.isAMissingValue()) { addSpace = tv.getType() != ValueType.SEPARATOR; break; } } } valueString.append(v + (addSpace ? " " : "")); } return valueString.toString().trim(); } int hashCode = -1; @Override public int hashCode() { // Only calculate the hashCode once if (hashCode == -1) { final int prime = 31; int result = 1; result = prime * result + locationInfo.hashCode(); result = prime * result + ((declarationValues == null) ? 0 : declarationValues .hashCode()); result = prime * result + (isCommaSeparatedListOfValues ? 1231 : 1237); result = prime * result + (isImportant ? 1231 : 1237); result = prime * result + getNumberOfMissingValues(); result = prime * result + ((parentSelector == null) ? 0 : parentSelector.hashCode()); result = prime * result + ((property == null) ? 0 : property.hashCode()); hashCode = result; } return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MultiValuedDeclaration other = (MultiValuedDeclaration) obj; if (isCommaSeparatedListOfValues != other.isCommaSeparatedListOfValues) return false; if (isImportant != other.isImportant) return false; if (getNumberOfMissingValues() != other.getNumberOfMissingValues()) return false; if (property == null) { if (other.property != null) return false; } else if (!property.equals(other.property)) return false; if (locationInfo == null) { if (other.locationInfo != null) return false; } else if (!locationInfo.equals(other.locationInfo)) return false; if (parentSelector == null) { if (other.parentSelector != null) return false; } else if (!parentSelector.equals(other.parentSelector)) return false; if (declarationValues == null) { if (other.declarationValues != null) return false; } else if (!declarationValues.equals(other.declarationValues)) return false; return true; } @Override public Declaration clone() { List<DeclarationValue> values = new ArrayList<>(); for (DeclarationValue v : declarationValues) { values.add(v.clone()); } //return DeclarationFactory.getDeclaration(property, values, parentSelector, offset, length, isImportant, false); //return new Declaration(property, values, parentSelector, lineNumber, colNumber, isImportant, false); return new MultiValuedDeclaration(property, values, parentSelector, isImportant, true, locationInfo); } @Override protected boolean valuesEqual(Declaration otherDeclaration) { if (!(otherDeclaration instanceof MultiValuedDeclaration)) throw new RuntimeException("This method cannot be called on a declaration rather than MultiValuedDeclaration."); MultiValuedDeclaration otherMultiValuedDeclaration = (MultiValuedDeclaration)otherDeclaration; if (((Collection<DeclarationValue>)otherMultiValuedDeclaration.getRealValues()).size() != ((Collection<DeclarationValue>)getRealValues()).size()) return false; for (PropertyAndLayer propertyAndLayer : getAllSetPropertyAndLayers()) { Collection<DeclarationValue> valuesForThisStyleProperty = getDeclarationValuesForStyleProperty(propertyAndLayer); if (valuesForThisStyleProperty != null) { if (!valuesForThisStyleProperty.equals(otherMultiValuedDeclaration.getDeclarationValuesForStyleProperty(propertyAndLayer))) return false; } else { return false; } } return true; } @Override protected boolean valuesEquivalent(Declaration otherDeclaration) { if (!(otherDeclaration instanceof MultiValuedDeclaration)) throw new RuntimeException("This method cannot be called on a declaration rather than MultiValuedDeclaration."); MultiValuedDeclaration otherMultiValuedDeclaration = (MultiValuedDeclaration)otherDeclaration; Set<PropertyAndLayer> allSetPropertyAndLayers = getAllSetPropertyAndLayers(); Set<PropertyAndLayer> otherAllSetPropertyAndLayers = otherMultiValuedDeclaration.getAllSetPropertyAndLayers(); if (allSetPropertyAndLayers.size() != otherAllSetPropertyAndLayers.size()) return false; for (PropertyAndLayer propertyAndLayer : allSetPropertyAndLayers) { Collection<DeclarationValue> valuesForThisStyleProperty = getDeclarationValuesForStyleProperty(propertyAndLayer); Collection<DeclarationValue> valuesForOtherStyleProperty = otherMultiValuedDeclaration.getDeclarationValuesForStyleProperty(propertyAndLayer); if (valuesForThisStyleProperty != null && valuesForOtherStyleProperty != null) { if (valuesForThisStyleProperty.size() != valuesForOtherStyleProperty.size()) return false; /* * If the underlying object is a List, we have to take care of the order. * Otherwise, order is not important. */ if (valuesForThisStyleProperty instanceof List && valuesForOtherStyleProperty instanceof List) { List<DeclarationValue> values1List = (List<DeclarationValue>)valuesForThisStyleProperty; List<DeclarationValue> values2List = (List<DeclarationValue>)valuesForOtherStyleProperty; for (int i = 0; i < valuesForOtherStyleProperty.size(); i++) { if (!values1List.get(i).equivalent(values2List.get(i))) return false; } } else { // Order is not important List<DeclarationValue> values1List = new ArrayList<>(valuesForThisStyleProperty); List<DeclarationValue> values2List = new ArrayList<>(valuesForOtherStyleProperty); Set<Integer> checkedValues = new HashSet<>(); for (int i = 0; i < values1List.size(); i++) { boolean foundEquivalent = false; for (int j = 0; j < values2List.size(); j++) { // Check if we have not already mapped this value to another one if (checkedValues.contains(j)) continue; // Are the values are equivalent if (values1List.get(i).equivalent(values2List.get(j))) { foundEquivalent = true; checkedValues.add(j); break; } } if (!foundEquivalent) return false; } if (checkedValues.size() != values1List.size()) return false; } } else { return false; } } return true; } /** * Returns all the style properties possible for the values of this multi-valued declaration * @return */ @Override public Collection<String> getStyleProperties() { Set<String> toReturn = new HashSet<>(); for (PropertyAndLayer propertyAndLayer : getAllSetPropertyAndLayers()) toReturn.add(propertyAndLayer.getPropertyName()); return toReturn; } /** * Returns a Collection of DeclarationValue's for the given property. * For the multi-valued properties, it returns the values corresponding to the first layer. * Returns null if such a DeclarationValue is not found. * @param styleProperty * @return */ public Collection<DeclarationValue> getDeclarationValuesForStyleProperty(String styleProperty) { return getDeclarationValuesForStyleProperty(styleProperty, 1); } /** * Returns a Collection of DeclarationValue's for the given property, in the given layer. * Returns null if such a DeclarationValue is not found. * @param styleProperty * @return */ @Override public Collection<DeclarationValue> getDeclarationValuesForStyleProperty(String styleProperty, int forLayer) { return stylePropertyToDeclarationValueMap.get(new PropertyAndLayer(styleProperty, forLayer)); } @Override public Map<String, List<Collection<DeclarationValue>>> getPropertyToValuesMap() { Map<String, List<Collection<DeclarationValue>>> toReturn = new HashMap<>(); for (PropertyAndLayer propertyAndLayer : getAllSetPropertyAndLayers()) { Collection<DeclarationValue> values = getDeclarationValuesForStyleProperty(propertyAndLayer); List<Collection<DeclarationValue>> declarationValues = toReturn.get(propertyAndLayer.getPropertyName()); if (declarationValues == null) { declarationValues = new ArrayList<Collection<DeclarationValue>>(); toReturn.put(propertyAndLayer.getPropertyName(), declarationValues); } declarationValues.set(propertyAndLayer.getPropertyLayer() - 1, values); } return toReturn; } protected Map<Integer, List<PropertyAndLayer>> getLayerToPropertyAndLayerMap() { Map<Integer, List<PropertyAndLayer>> layerToPropertyMapper = new HashMap<>(); for (PropertyAndLayer propertyAndLayer : getAllSetPropertyAndLayers()) { List<PropertyAndLayer> currentList = layerToPropertyMapper.get(propertyAndLayer.getPropertyLayer()); if (currentList == null) { currentList = new ArrayList<>(); layerToPropertyMapper.put(propertyAndLayer.getPropertyLayer(), currentList); } currentList.add(propertyAndLayer); } return layerToPropertyMapper; } @Override public Iterable<DeclarationValue> getDeclarationValues() { return declarationValues; } @Override public int getNumberOfValueLayers() { Set<Integer> layer = new HashSet<>(); for (PropertyAndLayer propertyAndLayer : getAllSetPropertyAndLayers()) { if (propertyAndLayer.getPropertyLayer() > -1) layer.add(propertyAndLayer.getPropertyLayer()); } return layer.size(); } @Override public Set<PropertyAndLayer> getAllSetPropertyAndLayers() { Set<PropertyAndLayer> allSetPropertyAndLayers = new HashSet<>(); for (DeclarationValue value : declarationValues) { if (value.getCorrespondingStyleProperty() != null) allSetPropertyAndLayers.add(new PropertyAndLayer(value.getCorrespondingStyleProperty(), value.getCorrespondingStyleLayer())); } return allSetPropertyAndLayers; } }