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.logging.log4j.Logger; 047import org.apache.logging.log4j.LogManager; 048 049import org.openimaj.citation.ReferenceListener; 050import org.openimaj.citation.annotation.Reference; 051import org.openimaj.citation.annotation.output.StandardFormatters; 052import org.openimaj.experiment.agent.TimeTracker; 053import org.openimaj.experiment.annotations.DatasetDescription; 054import org.openimaj.experiment.annotations.DependentVariable; 055import org.openimaj.experiment.annotations.Experiment; 056import org.openimaj.experiment.annotations.IndependentVariable; 057import org.openimaj.experiment.evaluation.AnalysisResult; 058import org.openimaj.util.array.ArrayUtils; 059 060import com.bethecoder.ascii_table.ASCIITable; 061import com.bethecoder.ascii_table.ASCIITableHeader; 062 063/** 064 * The recorded context of an experiment. 065 * 066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 067 */ 068public class ExperimentContext { 069 /** 070 * Representation of a recorded variable in an experiment (i.e. a field 071 * annotated with {@link IndependentVariable} or {@link DependentVariable}). 072 * 073 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 074 */ 075 public static class Variable { 076 /** 077 * The variables identifier taken from the annotation, or the variables 078 * name if the annotation has the default identifier. 079 */ 080 public String identifier; 081 082 Variable(String identifier) { 083 this.identifier = identifier; 084 } 085 } 086 087 private static final Logger logger = LogManager.getLogger(ExperimentContext.class); 088 089 private boolean isLocked; 090 091 private RunnableExperiment experiment; 092 private Date dateCompleted; 093 private Class<?> exptClass; 094 private Experiment experimentDetails; 095 private Set<Reference> bibliography; 096 private Map<String, SummaryStatistics> timingInfo; 097 private Map<Variable, Field> independentVariables = new HashMap<Variable, Field>(); 098 private Map<Variable, Field> dependentVariables = new HashMap<Variable, Field>(); 099 100 protected ExperimentContext(RunnableExperiment experiment) { 101 this.experiment = experiment; 102 experimentDetails = getExperiment(experiment); 103 exptClass = experiment.getClass(); 104 105 readVariables(experiment); 106 } 107 108 private Experiment getExperiment(RunnableExperiment experiment) { 109 Class<?> exptClass = experiment.getClass(); 110 111 while (exptClass != null) { 112 final Experiment ann = exptClass.getAnnotation(Experiment.class); 113 114 if (ann != null) 115 return ann; 116 117 exptClass = exptClass.getSuperclass(); 118 } 119 120 return null; 121 } 122 123 private void readVariables(RunnableExperiment expt) { 124 Class<?> exptClass = expt.getClass(); 125 126 while (exptClass != null) { 127 128 for (final Field field : exptClass.getDeclaredFields()) { 129 final IndependentVariable iv = field.getAnnotation(IndependentVariable.class); 130 final DependentVariable dv = field.getAnnotation(DependentVariable.class); 131 132 if (iv != null && dv != null) 133 throw new RuntimeException("Invalid experiment! The field " + field 134 + " cannot be both a dependent and independent variable."); 135 136 if (iv != null) { 137 String id = iv.identifier(); 138 if (id == null || id.length() == 0) 139 id = field.getName(); 140 141 this.independentVariables.put(new Variable(id), field); 142 } 143 144 if (dv != null) { 145 String id = dv.identifier(); 146 if (id == null || id.length() == 0) 147 id = field.getName(); 148 149 this.dependentVariables.put(new Variable(id), field); 150 } 151 } 152 153 exptClass = exptClass.getSuperclass(); 154 } 155 } 156 157 protected void lock() { 158 isLocked = true; 159 this.bibliography = ReferenceListener.getReferences(); 160 this.timingInfo = TimeTracker.getTimes(); 161 this.dateCompleted = new Date(); 162 } 163 164 /** 165 * Get the bibliography for the experiment. 166 * 167 * @return the bibliography 168 */ 169 public Set<Reference> getBibliography() { 170 if (!isLocked) 171 this.bibliography = ReferenceListener.getReferences(); 172 173 return bibliography; 174 } 175 176 /** 177 * Get the timing information for the experiment. 178 * 179 * @return the timing information 180 */ 181 public Map<String, SummaryStatistics> getTimingInfo() { 182 if (!isLocked) 183 this.timingInfo = TimeTracker.getTimes(); 184 185 return timingInfo; 186 } 187 188 /** 189 * Get the independent variables of the experiment and their values at the 190 * time this method is called. 191 * 192 * @return the independent variables 193 */ 194 public Map<Variable, Object> getIndependentVariables() { 195 final Map<Variable, Object> vars = new HashMap<Variable, Object>(); 196 197 for (final Entry<Variable, Field> e : this.independentVariables.entrySet()) { 198 final Field field = e.getValue(); 199 field.setAccessible(true); 200 Object value; 201 try { 202 value = field.get(this.experiment); 203 vars.put(e.getKey(), value); 204 } catch (final Exception ex) { 205 logger.warn(ex); 206 vars.put(e.getKey(), null); 207 } 208 } 209 210 return vars; 211 } 212 213 /** 214 * Get the dependent variables of the experiment and their values at the 215 * time this method is called. 216 * 217 * @return the dependent variables 218 */ 219 public Map<Variable, Object> getDependentVariables() { 220 final Map<Variable, Object> vars = new HashMap<Variable, Object>(); 221 222 for (final Entry<Variable, Field> e : this.dependentVariables.entrySet()) { 223 final Field field = e.getValue(); 224 field.setAccessible(true); 225 Object value; 226 try { 227 value = field.get(this.experiment); 228 vars.put(e.getKey(), value); 229 } catch (final Exception ex) { 230 logger.warn(ex); 231 vars.put(e.getKey(), null); 232 } 233 } 234 235 return vars; 236 } 237 238 private String getExptInfoTable() { 239 final Date dc = dateCompleted == null ? new Date() : dateCompleted; 240 241 final List<String[]> data = new ArrayList<String[]>(); 242 data.add(new String[] { "Class", exptClass.getName() }); 243 data.add(new String[] { "Report compiled", new SimpleDateFormat().format(dc) }); 244 245 if (experimentDetails != null) { 246 data.add(new String[] { "Author", WordUtils.wrap(experimentDetails.author(), exptClass.getName().length()) }); 247 data.add(new String[] { "Created on", experimentDetails.dateCreated() }); 248 data.add(new String[] { "Description", WordUtils.wrap(experimentDetails.description(), exptClass.getName() 249 .length()) }); 250 } 251 252 final ASCIITableHeader[] header = { 253 new ASCIITableHeader("", ASCIITable.ALIGN_RIGHT), 254 new ASCIITableHeader("", ASCIITable.ALIGN_LEFT) 255 }; 256 257 String table = ASCIITable.getInstance().getTable(header, data.toArray(new String[data.size()][])); 258 259 final int width = table.indexOf("\n") + 1; 260 table = table.substring(2 * width); 261 262 return table; 263 } 264 265 private String getTimingTable() { 266 final ASCIITableHeader[] header = { new ASCIITableHeader("Experimental Timing", ASCIITable.ALIGN_CENTER) }; 267 final String[][] data = formatAsTable(TimeTracker.format(timingInfo)); 268 return ASCIITable.getInstance().getTable(header, data); 269 } 270 271 private String getBibliographyTable() { 272 final ASCIITableHeader[] header = { new ASCIITableHeader("Bibliography", ASCIITable.ALIGN_LEFT) }; 273 String refs = StandardFormatters.STRING.format(bibliography); 274 275 refs = WordUtils.wrap(refs, Math.max(exptClass.getName().length() + 10, 72), SystemUtils.LINE_SEPARATOR + " ", 276 true); 277 278 final String[][] data = formatAsTable(refs); 279 return ASCIITable.getInstance().getTable(header, data); 280 } 281 282 private String[][] formatAsTable(String data) { 283 final String[] splits = data.trim().split("\\r?\\n"); 284 285 final String[][] out = new String[splits.length][]; 286 for (int i = 0; i < splits.length; i++) { 287 out[i] = new String[] { splits[i] }; 288 } 289 290 return out; 291 } 292 293 private String getIndependentVariablesTable() { 294 final ASCIITableHeader[] header = { new ASCIITableHeader("Independent Variables", ASCIITable.ALIGN_CENTER) }; 295 final String[][] data = formatAsTable(formatVariables(getIndependentVariables())); 296 return ASCIITable.getInstance().getTable(header, data); 297 } 298 299 private String formatVariables(Map<Variable, Object> vars) { 300 final List<String[]> data = new ArrayList<String[]>(); 301 302 for (final Entry<Variable, Object> e : vars.entrySet()) { 303 final String id = e.getKey().identifier; 304 final String[] val = formatValue(e.getValue()); 305 306 data.add(new String[] { id, val[0] }); 307 for (int i = 1; i < val.length; i++) 308 data.add(new String[] { "", val[i] }); 309 } 310 311 final ASCIITableHeader[] header = { 312 new ASCIITableHeader("", ASCIITable.ALIGN_RIGHT), 313 new ASCIITableHeader("", ASCIITable.ALIGN_LEFT) 314 }; 315 316 String table = ASCIITable.getInstance().getTable(header, data.toArray(new String[data.size()][])); 317 318 final int width = table.indexOf("\n") + 1; 319 table = table.substring(2 * width); 320 321 return table; 322 } 323 324 private String getDependentVariablesTable() { 325 final ASCIITableHeader[] header = { new ASCIITableHeader("Dependent Variables", ASCIITable.ALIGN_CENTER) }; 326 final String[][] data = formatAsTable(formatVariables(getDependentVariables())); 327 return ASCIITable.getInstance().getTable(header, data); 328 } 329 330 private String[] formatValue(Object value) { 331 // is it a dataset? 332 if (value.getClass().getAnnotation(DatasetDescription.class) != null) { 333 final DatasetDescription d = value.getClass().getAnnotation(DatasetDescription.class); 334 335 final List<String[]> data = new ArrayList<String[]>(); 336 data.add(new String[] { "Name", d.name() }); 337 338 final String[] description = WordUtils.wrap(d.description(), exptClass.getName().length() - 20).split( 339 "\\r?\\n"); 340 data.add(new String[] { "Description", description[0] }); 341 for (int i = 1; i < description.length; i++) 342 data.add(new String[] { "", description[i] }); 343 344 final ASCIITableHeader[] header = { 345 new ASCIITableHeader("", ASCIITable.ALIGN_RIGHT), 346 new ASCIITableHeader("", ASCIITable.ALIGN_LEFT) 347 }; 348 349 String table = ASCIITable.getInstance().getTable(header, data.toArray(new String[data.size()][])); 350 351 final int width = table.indexOf("\n") + 1; 352 table = table.substring(2 * width); 353 354 return table.split("\\r?\\n"); 355 } 356 357 // is it an analysis result? 358 if (value instanceof AnalysisResult) { 359 // return 360 // ((AnalysisResult)value).getDetailReport().split("\\r?\\n"); 361 return ((AnalysisResult) value).getSummaryReport().split("\\r?\\n"); 362 } 363 364 if (value.getClass().isArray()) { 365 final int length = Array.getLength(value); 366 if (length == 0) 367 return new String[] { "" }; 368 369 String str = Array.get(value, 0).toString(); 370 for (int i = 1; i < length; i++) { 371 str += ", " + Array.get(value, i); 372 } 373 return WordUtils.wrap(str, exptClass.getName().length() - 20).split("\\r?\\n"); 374 } 375 376 // otherwise use toString 377 return value.toString().split("\\r?\\n"); 378 } 379 380 @Override 381 public String toString() { 382 final String[][] exptinfo = formatAsTable(getExptInfoTable()); 383 final String[][] timeInfo = formatAsTable(getTimingTable()); 384 final String[][] ivInfo = formatAsTable(getIndependentVariablesTable()); 385 final String[][] dvInfo = formatAsTable(getDependentVariablesTable()); 386 final String[][] biblInfo = formatAsTable(getBibliographyTable()); 387 388 final String[][] data = ArrayUtils.concatenate(exptinfo, timeInfo, ivInfo, dvInfo, biblInfo); 389 final ASCIITableHeader[] header = { new ASCIITableHeader("Experiment Context", ASCIITable.ALIGN_LEFT) }; 390 391 return ASCIITable.getInstance().getTable(header, data); 392 } 393}