001: package org.uispec4j.utils;
002:
003: import junit.framework.AssertionFailedError;
004:
005: import java.awt.*;
006: import java.lang.reflect.Field;
007: import java.lang.reflect.Modifier;
008: import java.util.HashMap;
009: import java.util.Map;
010:
011: /**
012: * Utility for checking colors given either hexa or "natural language" string descriptions.
013: * <p>For all assertEquals/equals methods, the "expected" parameter can be either:
014: * <ul>
015: * <li>A <code>Color</code> object specifying the exact color</li>
016: * <li>A <code>String</code> object containing the hexadecimal description of the exact color</li>
017: * <li>A <code>String</code> object containing the name of a color for an approximate comparison.
018: * The authorized names are those of the predefined Color objects defined in the Java
019: * <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Color.html">Color</a> class.</li>
020: * </ul>
021: * </p>
022: *
023: * @see <a href="http://www.uispec4j.org/usingcolors.html">Using colors</a>
024: */
025: public final class ColorUtils {
026: private static final Map nameToColorMap = buildNameToColorMap();
027: static final String UNEXPECTED_COLOR_CLASS = "Expected color should be an instance of Color or String";
028:
029: private ColorUtils() {
030: }
031:
032: public static void assertEquals(Object expected, Color actual) {
033: assertEquals(null, expected, actual);
034: }
035:
036: public static void assertEquals(String message, Object expected,
037: Color actual) {
038: if (!equals(expected, actual)) {
039: String errorMsg = (message != null ? message + " " : "")
040: + "expected:<" + getColorDescription(expected)
041: + "> but was:<" + getColorDescription(actual) + ">";
042: throw new AssertionFailedError(errorMsg);
043: }
044: }
045:
046: public static boolean equals(Object expectedColor, Color actual) {
047: if (expectedColor instanceof Color) {
048: return expectedColor.equals(actual);
049: } else if (expectedColor instanceof String) {
050: return equals((String) expectedColor, actual);
051: }
052: throw new IllegalArgumentException(UNEXPECTED_COLOR_CLASS);
053: }
054:
055: public static boolean equals(String expectedColor, Color actual) {
056: Color foundColor = (Color) nameToColorMap.get(expectedColor
057: .toUpperCase());
058: if (foundColor != null) {
059: return equals(foundColor, actual, true);
060: }
061:
062: try {
063: foundColor = getColor(expectedColor);
064: return equals(foundColor, actual, false);
065: } catch (NumberFormatException e) {
066: throw new IllegalArgumentException("'" + expectedColor
067: + "' does not seem to be a color");
068: }
069: }
070:
071: public static Color getColor(String hexaString) {
072: return new Color(Integer.parseInt(hexaString, 16));
073: }
074:
075: /**
076: * Returns a normalized string description given a Color or another String description
077: */
078: public static String getColorDescription(Object color) {
079: if (color instanceof Color) {
080: return getColorDescription((Color) color);
081: } else if (color instanceof String) {
082: return getColorDescription((String) color);
083: }
084: throw new IllegalArgumentException(UNEXPECTED_COLOR_CLASS);
085: }
086:
087: /**
088: * Returns a description string for a given Color object
089: */
090: public static String getColorDescription(Color color) {
091: return toHexString(color).toUpperCase();
092: }
093:
094: /**
095: * Normalizes a given color description string
096: */
097: public static String getColorDescription(String color) {
098: return color.toUpperCase();
099: }
100:
101: private static boolean equals(Color expected, Color actual,
102: boolean matchBySimilarity) {
103: if (matchBySimilarity) {
104: return computeHSBDistance(expected, actual) < 0.9;
105: } else {
106: return expected.equals(actual);
107: }
108: }
109:
110: private static double computeHSBDistance(Color expected,
111: Color actual) {
112: float[] expectedHSB = toHSB(expected);
113: float[] actualHSB = toHSB(actual);
114:
115: return Math.sqrt(Math.pow((expectedHSB[0] - actualHSB[0]) * 20,
116: 2)
117: + Math.pow(expectedHSB[1] - actualHSB[1], 2)
118: + Math.pow((expectedHSB[2] - actualHSB[2]) * 2, 2));
119: }
120:
121: private static float[] toHSB(Color aColor) {
122: return Color.RGBtoHSB(aColor.getRed(), aColor.getGreen(),
123: aColor.getBlue(), null);
124: }
125:
126: private static Map buildNameToColorMap() {
127: Map colorMap = new HashMap();
128: Field[] fields = Color.class.getDeclaredFields();
129: for (int i = 0; i < fields.length; i++) {
130: Field field = fields[i];
131: if (isConstantColorField(field)) {
132: try {
133: Color color = (Color) field.get(null);
134: colorMap.put(field.getName().toUpperCase(), color);
135: } catch (IllegalAccessException e) {
136: // Should not occur because the field is public and static
137: }
138: }
139: }
140: return colorMap;
141: }
142:
143: private static boolean isConstantColorField(Field field) {
144: return Modifier.isPublic(field.getModifiers())
145: && Modifier.isStatic(field.getModifiers())
146: && Color.class == field.getType();
147: }
148:
149: private static String toHexString(Color color) {
150: return Integer.toHexString(color.getRGB()).substring(2);
151: }
152: }
|