package com.github.sommeri.less4j.core.ast;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.github.sommeri.less4j.core.ast.annotations.NotAstProperty;
import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree;
//the system would be nicer and more consistent if they all would be cloneable.
public class NumberExpression extends Expression implements Cloneable {
private String originalString;
private Dimension dimension = Dimension.NUMBER;
private Double valueAsDouble;
private String suffix = "";
private boolean expliciteSign = false;
public NumberExpression(HiddenTokenAwareTree token) {
super(token);
}
public NumberExpression(HiddenTokenAwareTree token, String originalString) {
super(token);
this.originalString = originalString;
}
private NumberExpression(HiddenTokenAwareTree token, String originalString, Dimension dimension) {
this(token, originalString);
this.dimension = dimension;
}
public NumberExpression(HiddenTokenAwareTree token, String lowerCaseValue, Dimension repeater, boolean expliciteSign) {
this(token, lowerCaseValue, repeater);
this.expliciteSign = expliciteSign;
}
public NumberExpression(HiddenTokenAwareTree token, Double valueAsDouble, String suffix, String originalString, Dimension dimension) {
this(token, originalString, dimension);
this.valueAsDouble = valueAsDouble;
this.suffix = suffix;
}
public String getOriginalString() {
return originalString;
}
public void setOriginalString(String originalString) {
this.originalString = originalString;
}
public Dimension getDimension() {
return dimension;
}
public Double getValueAsDouble() {
return valueAsDouble;
}
public void setValueAsDouble(Double number) {
this.valueAsDouble = number;
}
public void setDimension(Dimension dimension) {
this.dimension = dimension;
}
public String getSuffix() {
return suffix;
}
public boolean hasExpliciteSign() {
return expliciteSign;
}
public void setExpliciteSign(boolean expliciteSign) {
this.expliciteSign = expliciteSign;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public void negate() {
if (valueAsDouble == null)
return;
valueAsDouble = valueAsDouble * -1;
}
public boolean convertibleTo(NumberExpression second) {
String toSuffix = second.getSuffix();
return convertibleTo(toSuffix);
}
public boolean convertibleTo(String toSuffix) {
String fromSuffix = getSuffix();
if (toSuffix==null || toSuffix.isEmpty() || fromSuffix==null || fromSuffix.isEmpty())
return true;
fromSuffix = fromSuffix.toLowerCase();
toSuffix = toSuffix.toLowerCase();
if (fromSuffix.equals(toSuffix))
return true;
Map<String, Number> conversions = getDimension().getConversions();
return conversions.containsKey(toSuffix.toLowerCase()) && conversions.containsKey(fromSuffix.toLowerCase());
}
public NumberExpression convertIfPossible(String toSuffix) {
String fromSuffix = getSuffix();
if (toSuffix==null || toSuffix.isEmpty() || fromSuffix==null || fromSuffix.isEmpty())
return this;
fromSuffix = fromSuffix.toLowerCase();
toSuffix = toSuffix.toLowerCase();
if (fromSuffix.equals(toSuffix))
return this;
Map<String, Number> conversions = getDimension().getConversions();
Number multiplier = conversions.get(fromSuffix);
Number divisor = conversions.get(toSuffix);
if (multiplier != null && divisor != null) {
HiddenTokenAwareTree token = getUnderlyingStructure();
Double value = getValueAsDouble();
return new NumberExpression(token, value * multiplier.doubleValue() / divisor.doubleValue(), toSuffix, null, Dimension.forSuffix(toSuffix));
} else {
return this;
}
}
@Override
@NotAstProperty
public List<? extends ASTCssNode> getChilds() {
return Collections.emptyList();
}
public boolean hasOriginalString() {
return getOriginalString() != null;
}
@Override
public ASTCssNodeType getType() {
return ASTCssNodeType.NUMBER;
}
public enum Dimension {
NUMBER, PERCENTAGE, LENGTH, EMS, EXS, ANGLE, TIME, FREQ, REPEATER, UNKNOWN;
private Map<String, Number> conversions;
public static Dimension forSuffix(String suffix) {
if (suffix == null || suffix.isEmpty()) {
return NUMBER;
} else if (suffix.equals("%")) {
return PERCENTAGE;
} else if (suffix.equals("px") || suffix.equals("cm") || suffix.equals("mm") || suffix.equals("in") || suffix.equals("pt") || suffix.equals("pc")) {
return LENGTH;
} else if (suffix.equals("em")) {
return EMS;
} else if (suffix.equals("ex")) {
return EXS;
} else if (suffix.equals("deg") || suffix.equals("rad") || suffix.equals("grad") || suffix.equals("turn")) {
return ANGLE;
} else if (suffix.equals("ms") || suffix.equals("s")) {
return TIME;
} else if (suffix.equals("khz") || suffix.equals("hz")) {
return FREQ;
} else {
return UNKNOWN;
}
}
public Map<String, Number> getConversions() {
if (conversions != null)
return conversions;
HashMap<String, Number> result = new HashMap<String, Number>();
switch (this) {
case LENGTH:
result.put("m", 1);
result.put("cm", 0.01);
result.put("mm", 0.001);
result.put("in", 0.0254);
result.put("pt", 0.0254 / 72);
result.put("pc", 0.0254 / 72 * 12);
break;
case TIME:
result.put("s", 1);
result.put("ms", 0.001);
break;
case ANGLE:
result.put("rad", 1.0 / (2 * Math.PI));
result.put("deg", 1.0 / 360);
result.put("grad", 1.0 / 400);
result.put("turn", 1);
break;
case FREQ:
result.put("khz", 1);
result.put("hz", 0.001);
break;
case PERCENTAGE:
result.put("%", 1);
break;
case EMS:
result.put("em", 1);
break;
case EXS:
result.put("ex", 1);
break;
case REPEATER:
result.put("n", 1);
break;
case NUMBER:
result.put("", 1);
break;
case UNKNOWN:
break;
}
this.conversions = result;
return result;
}
}
@Override
public String toString() {
if (originalString != null)
return originalString;
return "" + valueAsDouble + suffix;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dimension == null) ? 0 : dimension.hashCode());
result = prime * result + (expliciteSign ? 1231 : 1237);
result = prime * result + ((suffix == null) ? 0 : suffix.hashCode());
result = prime * result + ((valueAsDouble == null) ? 0 : valueAsDouble.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;
NumberExpression other = (NumberExpression) obj;
if (dimension != other.dimension)
return false;
if (expliciteSign != other.expliciteSign)
return false;
if (suffix == null) {
if (other.suffix != null)
return false;
} else if (!suffix.equals(other.suffix))
return false;
if (valueAsDouble == null) {
if (other.valueAsDouble != null)
return false;
} else if (!valueAsDouble.equals(other.valueAsDouble))
return false;
return true;
}
@Override
public NumberExpression clone() {
NumberExpression clone = (NumberExpression) super.clone();
return clone;
}
}