001    /*--------------------------------------------------------------------------+
002    $Id: Assessment.java 27816 2010-05-20 16:13:15Z hummelb $
003    |                                                                          |
004    | Copyright 2005-2010 Technische Universitaet Muenchen                     |
005    |                                                                          |
006    | Licensed under the Apache License, Version 2.0 (the "License");          |
007    | you may not use this file except in compliance with the License.         |
008    | You may obtain a copy of the License at                                  |
009    |                                                                          |
010    |    http://www.apache.org/licenses/LICENSE-2.0                            |
011    |                                                                          |
012    | Unless required by applicable law or agreed to in writing, software      |
013    | distributed under the License is distributed on an "AS IS" BASIS,        |
014    | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015    | See the License for the specific language governing permissions and      |
016    | limitations under the License.                                           |
017    +--------------------------------------------------------------------------*/
018    package edu.tum.cs.commons.assessment;
019    
020    import java.io.Serializable;
021    import java.util.Collection;
022    
023    import edu.tum.cs.commons.clone.IDeepCloneable;
024    
025    /**
026     * This class stores an assessment. An assessment is a multiset of traffic light
027     * colors (i.e. a mapping from traffic light colors to non-negative integers).
028     * 
029     * @author Benjamin Hummel
030     * @author $Author: hummelb $
031     * @version $Rev: 27816 $
032     * @levd.rating GREEN Hash: E1A8D09889F5CFB5BAD863BEB224644F
033     */
034    public class Assessment implements Cloneable, IDeepCloneable, Serializable {
035    
036            /** The "multimap". */
037            private int[] mapping = new int[ETrafficLightColor.values().length];
038    
039            /** The assessment message. */
040            private String message;
041    
042            /**
043             * Creates an empty assessment (i.e. one with all entries set to 0).
044             */
045            public Assessment() {
046                    // Do nothing, but keep it to have a default constructor.
047            }
048    
049            /**
050             * Create an assessment with a single color entry.
051             * 
052             * @param color
053             *            the color included in this assessment.
054             */
055            public Assessment(ETrafficLightColor color) {
056                    add(color);
057            }
058    
059            /**
060             * Create an assessement with a assessment message.
061             */
062            public Assessment(ETrafficLightColor color, String message) {
063                    add(color);
064                    this.message = message;
065            }
066    
067            /**
068             * Add a single entry of this color to this assessment.
069             * 
070             * @param color
071             *            the color added to this assessment.
072             */
073            public void add(ETrafficLightColor color) {
074                    mapping[color.ordinal()]++;
075            }
076    
077            /**
078             * Add a single entry of this color to this assessment.
079             * 
080             * @param color
081             *            the color added to this assessment.
082             * @param count
083             *            how often to add this color to the assessment.
084             */
085            public void add(ETrafficLightColor color, int count) {
086                    if (count < 0) {
087                            throw new IllegalArgumentException("Count must be non-negative!");
088                    }
089                    mapping[color.ordinal()] += count;
090            }
091    
092            /**
093             * Merge the provided assessment into this, i.e. increase all trafic light
094             * color counts by the values in the provided asseessment.
095             * 
096             * @param a
097             *            the assessment to merge in.
098             */
099            public void add(Assessment a) {
100                    for (int i = 0; i < mapping.length; ++i) {
101                            mapping[i] += a.mapping[i];
102                    }
103            }
104    
105            /**
106             * @param color
107             *            the color whose frequency to read.
108             * @return the number of occurrences of the provided color in this
109             *         assessment.
110             */
111            public int getColorFrequency(ETrafficLightColor color) {
112                    return mapping[color.ordinal()];
113            }
114    
115            /**
116             * @return the most dominant color. If there is a RED entry, RED is
117             *         returned. Otherwise, if there is a YELLOW entry, YELLOW is
118             *         returned. If there is neither a RED OR YELLOW entry, but a GREEN
119             *         one, GREEN is returned. If no color is there, UNKNOWN is
120             *         returned.
121             * 
122             *         This method relies on the fact that the entries in
123             *         {@link ETrafficLightColor} are ordered according to their
124             *         dominance.
125             */
126            public ETrafficLightColor getDominantColor() {
127                    for (int i = 0; i < ETrafficLightColor.values().length; ++i) {
128                            if (mapping[i] > 0) {
129                                    return ETrafficLightColor.values()[i];
130                            }
131                    }
132                    return ETrafficLightColor.UNKNOWN;
133            }
134    
135            /**
136             * @return the color that is most frequent in this assessment. If all
137             *         frequencies are 0, UNKNOWN is returned. If there are ties, the
138             *         more dominant (see {@link #getDominantColor()}) one is returned.
139             */
140            public ETrafficLightColor getMostFrequentColor() {
141                    ETrafficLightColor result = null;
142                    int resultCount = 0;
143    
144                    for (int i = 0; i < mapping.length; ++i) {
145                            if (mapping[i] > resultCount) {
146                                    resultCount = mapping[i];
147                                    result = ETrafficLightColor.values()[i];
148                            }
149                    }
150    
151                    if (result != null) {
152                            return result;
153                    }
154                    return ETrafficLightColor.UNKNOWN;
155            }
156    
157            /** {@inheritDoc} */
158            @Override
159            public String toString() {
160                    int sum = 0;
161                    for (int i = 0; i < mapping.length; ++i) {
162                            sum += mapping[i];
163                    }
164    
165                    if (sum == 0) {
166                            return "";
167                    }
168    
169                    if (sum == 1) {
170                            for (int i = 0; i < mapping.length; ++i) {
171                                    if (mapping[i] > 0) {
172                                            return ETrafficLightColor.values()[i].toString();
173                                    }
174                            }
175                    }
176    
177                    StringBuilder builder = new StringBuilder("[");
178                    appendColor(builder, ETrafficLightColor.GREEN);
179                    builder.append(", ");
180                    appendColor(builder, ETrafficLightColor.YELLOW);
181                    builder.append(", ");
182                    appendColor(builder, ETrafficLightColor.RED);
183                    builder.append("]");
184                    return builder.toString();
185            }
186    
187            /**
188             * append a string containing the color and its frequency to the given
189             * builder
190             * 
191             * @param builder
192             * @param color
193             */
194            private void appendColor(StringBuilder builder, ETrafficLightColor color) {
195                    builder.append(color.toString().substring(0, 1));
196                    builder.append(": ");
197                    builder.append(getColorFrequency(color));
198            }
199    
200            /** {@inheritDoc} */
201            @Override
202            protected Object clone() throws CloneNotSupportedException {
203                    Assessment a = (Assessment) super.clone();
204                    a.mapping = a.mapping.clone();
205                    return a;
206            }
207    
208            /** {@inheritDoc} */
209            public Assessment deepClone() {
210                    Assessment a = new Assessment();
211                    a.add(this);
212                    return a;
213            }
214    
215            /** {@inheritDoc} */
216            @Override
217            public boolean equals(Object obj) {
218                    if (!(obj instanceof Assessment)) {
219                            return false;
220                    }
221    
222                    Assessment a = (Assessment) obj;
223                    for (int i = 0; i < mapping.length; ++i) {
224                            if (mapping[i] != a.mapping[i]) {
225                                    return false;
226                            }
227                    }
228                    return true;
229            }
230    
231            /** {@inheritDoc} */
232            @Override
233            public int hashCode() {
234                    int hash = 0;
235                    for (int i = 0; i < mapping.length; ++i) {
236                            /*
237                             * primes taken from
238                             * http://planetmath.org/encyclopedia/GoodHashTablePrimes.html
239                             */
240                            hash *= 97;
241                            hash += mapping[i];
242                            hash %= 50331653;
243                    }
244                    return hash;
245            }
246    
247            /**
248             * Get assessment message.
249             * 
250             * @return the message or <code>null</code> if this assessment has no
251             *         message.
252             */
253            public String getMessage() {
254                    return message;
255            }
256    
257            /**
258             * Set assessment message.
259             * 
260             * @param message
261             *            the message or <code>null</code> if this assessment has no
262             *            message.
263             */
264            public void setMessage(String message) {
265                    this.message = message;
266            }
267    
268            /** Aggregate assessments. */
269            public static Assessment aggregate(Collection<Assessment> values) {
270                    Assessment result = new Assessment();
271                    for (Assessment a : values) {
272                            result.add(a);
273                    }
274                    return result;
275            }
276    
277    }