001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.experiment; 031 032import java.lang.reflect.Array; 033import java.lang.reflect.Field; 034import java.text.SimpleDateFormat; 035import java.util.ArrayList; 036import java.util.Date; 037import java.util.HashMap; 038import java.util.List; 039import java.util.Map; 040import java.util.Map.Entry; 041import java.util.Set; 042 043import org.apache.commons.lang.SystemUtils; 044import org.apache.commons.lang.WordUtils; 045import org.apache.commons.math.stat.descriptive.SummaryStatistics; 046import org.apache.log4j.Logger; 047import org.openimaj.citation.ReferenceListener; 048import org.openimaj.citation.annotation.Reference; 049import org.openimaj.citation.annotation.output.StandardFormatters; 050import org.openimaj.experiment.agent.TimeTracker; 051import org.openimaj.experiment.annotations.DatasetDescription; 052import org.openimaj.experiment.annotations.DependentVariable; 053import org.openimaj.experiment.annotations.Experiment; 054import org.openimaj.experiment.annotations.IndependentVariable; 055import org.openimaj.experiment.evaluation.AnalysisResult; 056import org.openimaj.util.array.ArrayUtils; 057 058import com.bethecoder.ascii_table.ASCIITable; 059import com.bethecoder.ascii_table.ASCIITableHeader; 060 061/** 062 * The recorded context of an experiment. 063 * 064 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 065 */ 066public class ExperimentContext { 067 /** 068 * Representation of a recorded variable in an experiment (i.e. a field 069 * annotated with {@link IndependentVariable} or {@link DependentVariable}). 070 * 071 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 072 */ 073 public static class Variable { 074 /** 075 * The variables identifier taken from the annotation, or the variables 076 * name if the annotation has the default identifier. 077 */ 078 public String identifier; 079 080 Variable(String identifier) { 081 this.identifier = identifier; 082 } 083 } 084 085 private static final Logger logger = Logger.getLogger(ExperimentContext.class); 086 087 private boolean isLocked; 088 089 private RunnableExperiment experiment; 090 private Date dateCompleted; 091 private Class<?> exptClass; 092 private Experiment experimentDetails; 093 private Set<Reference> bibliography; 094 private Map<String, SummaryStatistics> timingInfo; 095 private Map<Variable, Field> independentVariables = new HashMap<Variable, Field>(); 096 private Map<Variable, Field> dependentVariables = new HashMap<Variable, Field>(); 097 098 protected ExperimentContext(RunnableExperiment experiment) { 099 this.experiment = experiment; 100 experimentDetails = getExperiment(experiment); 101 exptClass = experiment.getClass(); 102 103 readVariables(experiment); 104 } 105 106 private Experiment getExperiment(RunnableExperiment experiment) { 107 Class<?> exptClass = experiment.getClass(); 108 109 while (exptClass != null) { 110 final Experiment ann = exptClass.getAnnotation(Experiment.class); 111 112 if (ann != null) 113 return ann; 114 115 exptClass = exptClass.getSuperclass(); 116 } 117 118 return null; 119 } 120 121 private void readVariables(RunnableExperiment expt) { 122 Class<?> exptClass = expt.getClass(); 123 124 while (exptClass != null) { 125 126 for (final Field field : exptClass.getDeclaredFields()) { 127 final IndependentVariable iv = field.getAnnotation(IndependentVariable.class); 128 final DependentVariable dv = field.getAnnotation(DependentVariable.class); 129 130 if (iv != null && dv != null) 131 throw new RuntimeException("Invalid experiment! The field " + field 132 + " cannot be both a dependent and independent variable."); 133 134 if (iv != null) { 135 String id = iv.identifier(); 136 if (id == null || id.length() == 0) 137 id = field.getName(); 138 139 this.independentVariables.put(new Variable(id), field); 140 } 141 142 if (dv != null) { 143 String id = dv.identifier(); 144 if (id == null || id.length() == 0) 145 id = field.getName(); 146 147 this.dependentVariables.put(new Variable(id), field); 148 } 149 } 150 151 exptClass = exptClass.getSuperclass(); 152 } 153 } 154 155 protected void lock() { 156 isLocked = true; 157 this.bibliography = ReferenceListener.getReferences(); 158 this.timingInfo = TimeTracker.getTimes(); 159 this.dateCompleted = new Date(); 160 } 161 162 /** 163 * Get the bibliography for the experiment. 164 * 165 * @return the bibliography 166 */ 167 public Set<Reference> getBibliography() { 168 if (!isLocked) 169 this.bibliography = ReferenceListener.getReferences(); 170 171 return bibliography; 172 } 173 174 /** 175 * Get the timing information for the experiment. 176 * 177 * @return the timing information 178 */ 179 public Map<String, SummaryStatistics> getTimingInfo() { 180 if (!isLocked) 181 this.timingInfo = TimeTracker.getTimes(); 182 183 return timingInfo; 184 } 185 186 /** 187 * Get the independent variables of the experiment and their values at the 188 * time this method is called. 189 * 190 * @return the independent variables 191 */ 192 public Map<Variable, Object> getIndependentVariables() { 193 final Map<Variable, Object> vars = new HashMap<Variable, Object>(); 194 195 for (final Entry<Variable, Field> e : this.independentVariables.entrySet()) { 196 final Field field = e.getValue(); 197 field.setAccessible(true); 198 Object value; 199 try { 200 value = field.get(this.experiment); 201 vars.put(e.getKey(), value); 202 } catch (final Exception ex) { 203 logger.warn(ex); 204 vars.put(e.getKey(), null); 205 } 206 } 207 208 return vars; 209 } 210 211 /** 212 * Get the dependent variables of the experiment and their values at the 213 * time this method is called. 214 * 215 * @return the dependent variables 216 */ 217 public Map<Variable, Object> getDependentVariables() { 218 final Map<Variable, Object> vars = new HashMap<Variable, Object>(); 219 220 for (final Entry<Variable, Field> e : this.dependentVariables.entrySet()) { 221 final Field field = e.getValue(); 222 field.setAccessible(true); 223 Object value; 224 try { 225 value = field.get(this.experiment); 226 vars.put(e.getKey(), value); 227 } catch (final Exception ex) { 228 logger.warn(ex); 229 vars.put(e.getKey(), null); 230 } 231 } 232 233 return vars; 234 } 235 236 private String getExptInfoTable() { 237 final Date dc = dateCompleted == null ? new Date() : dateCompleted; 238 239 final List<String[]> data = new ArrayList<String[]>(); 240 data.add(new String[] { "Class", exptClass.getName() }); 241 data.add(new String[] { "Report compiled", new SimpleDateFormat().format(dc) }); 242 243 if (experimentDetails != null) { 244 data.add(new String[] { "Author", WordUtils.wrap(experimentDetails.author(), exptClass.getName().length()) }); 245 data.add(new String[] { "Created on", experimentDetails.dateCreated() }); 246 data.add(new String[] { "Description", WordUtils.wrap(experimentDetails.description(), exptClass.getName() 247 .length()) }); 248 } 249 250 final ASCIITableHeader[] header = { 251 new ASCIITableHeader("", ASCIITable.ALIGN_RIGHT), 252 new ASCIITableHeader("", ASCIITable.ALIGN_LEFT) 253 }; 254 255 String table = ASCIITable.getInstance().getTable(header, data.toArray(new String[data.size()][])); 256 257 final int width = table.indexOf("\n") + 1; 258 table = table.substring(2 * width); 259 260 return table; 261 } 262 263 private String getTimingTable() { 264 final ASCIITableHeader[] header = { new ASCIITableHeader("Experimental Timing", ASCIITable.ALIGN_CENTER) }; 265 final String[][] data = formatAsTable(TimeTracker.format(timingInfo)); 266 return ASCIITable.getInstance().getTable(header, data); 267 } 268 269 private String getBibliographyTable() { 270 final ASCIITableHeader[] header = { new ASCIITableHeader("Bibliography", ASCIITable.ALIGN_LEFT) }; 271 String refs = StandardFormatters.STRING.format(bibliography); 272 273 refs = WordUtils.wrap(refs, Math.max(exptClass.getName().length() + 10, 72), SystemUtils.LINE_SEPARATOR + " ", 274 true); 275 276 final String[][] data = formatAsTable(refs); 277 return ASCIITable.getInstance().getTable(header, data); 278 } 279 280 private String[][] formatAsTable(String data) { 281 final String[] splits = data.trim().split("\\r?\\n"); 282 283 final String[][] out = new String[splits.length][]; 284 for (int i = 0; i < splits.length; i++) { 285 out[i] = new String[] { splits[i] }; 286 } 287 288 return out; 289 } 290 291 private String getIndependentVariablesTable() { 292 final ASCIITableHeader[] header = { new ASCIITableHeader("Independent Variables", ASCIITable.ALIGN_CENTER) }; 293 final String[][] data = formatAsTable(formatVariables(getIndependentVariables())); 294 return ASCIITable.getInstance().getTable(header, data); 295 } 296 297 private String formatVariables(Map<Variable, Object> vars) { 298 final List<String[]> data = new ArrayList<String[]>(); 299 300 for (final Entry<Variable, Object> e : vars.entrySet()) { 301 final String id = e.getKey().identifier; 302 final String[] val = formatValue(e.getValue()); 303 304 data.add(new String[] { id, val[0] }); 305 for (int i = 1; i < val.length; i++) 306 data.add(new String[] { "", val[i] }); 307 } 308 309 final ASCIITableHeader[] header = { 310 new ASCIITableHeader("", ASCIITable.ALIGN_RIGHT), 311 new ASCIITableHeader("", ASCIITable.ALIGN_LEFT) 312 }; 313 314 String table = ASCIITable.getInstance().getTable(header, data.toArray(new String[data.size()][])); 315 316 final int width = table.indexOf("\n") + 1; 317 table = table.substring(2 * width); 318 319 return table; 320 } 321 322 private String getDependentVariablesTable() { 323 final ASCIITableHeader[] header = { new ASCIITableHeader("Dependent Variables", ASCIITable.ALIGN_CENTER) }; 324 final String[][] data = formatAsTable(formatVariables(getDependentVariables())); 325 return ASCIITable.getInstance().getTable(header, data); 326 } 327 328 private String[] formatValue(Object value) { 329 // is it a dataset? 330 if (value.getClass().getAnnotation(DatasetDescription.class) != null) { 331 final DatasetDescription d = value.getClass().getAnnotation(DatasetDescription.class); 332 333 final List<String[]> data = new ArrayList<String[]>(); 334 data.add(new String[] { "Name", d.name() }); 335 336 final String[] description = WordUtils.wrap(d.description(), exptClass.getName().length() - 20).split( 337 "\\r?\\n"); 338 data.add(new String[] { "Description", description[0] }); 339 for (int i = 1; i < description.length; i++) 340 data.add(new String[] { "", description[i] }); 341 342 final ASCIITableHeader[] header = { 343 new ASCIITableHeader("", ASCIITable.ALIGN_RIGHT), 344 new ASCIITableHeader("", ASCIITable.ALIGN_LEFT) 345 }; 346 347 String table = ASCIITable.getInstance().getTable(header, data.toArray(new String[data.size()][])); 348 349 final int width = table.indexOf("\n") + 1; 350 table = table.substring(2 * width); 351 352 return table.split("\\r?\\n"); 353 } 354 355 // is it an analysis result? 356 if (value instanceof AnalysisResult) { 357 // return 358 // ((AnalysisResult)value).getDetailReport().split("\\r?\\n"); 359 return ((AnalysisResult) value).getSummaryReport().split("\\r?\\n"); 360 } 361 362 if (value.getClass().isArray()) { 363 final int length = Array.getLength(value); 364 if (length == 0) 365 return new String[] { "" }; 366 367 String str = Array.get(value, 0).toString(); 368 for (int i = 1; i < length; i++) { 369 str += ", " + Array.get(value, i); 370 } 371 return WordUtils.wrap(str, exptClass.getName().length() - 20).split("\\r?\\n"); 372 } 373 374 // otherwise use toString 375 return value.toString().split("\\r?\\n"); 376 } 377 378 @Override 379 public String toString() { 380 final String[][] exptinfo = formatAsTable(getExptInfoTable()); 381 final String[][] timeInfo = formatAsTable(getTimingTable()); 382 final String[][] ivInfo = formatAsTable(getIndependentVariablesTable()); 383 final String[][] dvInfo = formatAsTable(getDependentVariablesTable()); 384 final String[][] biblInfo = formatAsTable(getBibliographyTable()); 385 386 final String[][] data = ArrayUtils.concatenate(exptinfo, timeInfo, ivInfo, dvInfo, biblInfo); 387 final ASCIITableHeader[] header = { new ASCIITableHeader("Experiment Context", ASCIITable.ALIGN_LEFT) }; 388 389 return ASCIITable.getInstance().getTable(header, data); 390 } 391}