Changeset 3070
- Timestamp:
- 01/19/12 13:56:29 (4 months ago)
- Location:
- AnalyzerBeans/trunk
- Files:
-
- 10 edited
-
cli/src/main/java/org/eobjects/analyzer/cli/CliArguments.java (modified) (1 diff)
-
cli/src/main/java/org/eobjects/analyzer/cli/CliOutputType.java (modified) (1 diff)
-
cli/src/main/java/org/eobjects/analyzer/cli/CliRunner.java (modified) (21 diffs)
-
cli/src/test/java/org/eobjects/analyzer/cli/MainTest.java (modified) (4 diffs)
-
core/src/main/java/org/eobjects/analyzer/job/concurrent/JobCompletionTaskListener.java (modified) (4 diffs)
-
core/src/main/java/org/eobjects/analyzer/job/concurrent/StatusAwareTaskListener.java (modified) (2 diffs)
-
core/src/main/java/org/eobjects/analyzer/job/runner/AnalysisResultFutureImpl.java (modified) (2 diffs)
-
core/src/main/java/org/eobjects/analyzer/result/AnalysisResult.java (modified) (2 diffs)
-
core/src/main/java/org/eobjects/analyzer/result/SimpleAnalysisResult.java (modified) (3 diffs)
-
core/src/main/java/org/eobjects/analyzer/result/renderer/RendererFactory.java (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
AnalyzerBeans/trunk/cli/src/main/java/org/eobjects/analyzer/cli/CliArguments.java
r3068 r3070 145 145 146 146 public CliOutputType getOutputType() { 147 if (output File == null) {147 if (outputType == null) { 148 148 return CliOutputType.TEXT; 149 149 } -
AnalyzerBeans/trunk/cli/src/main/java/org/eobjects/analyzer/cli/CliOutputType.java
r3068 r3070 34 34 TEXT(TextRenderingFormat.class), HTML(HtmlRenderingFormat.class), SERIALIZED(null); 35 35 36 private final Class<? extends RenderingFormat<? >> _renderingFormat;36 private final Class<? extends RenderingFormat<? extends CharSequence>> _renderingFormat; 37 37 38 private CliOutputType(Class<? extends RenderingFormat<? >> renderingFormat) {38 private CliOutputType(Class<? extends RenderingFormat<? extends CharSequence>> renderingFormat) { 39 39 _renderingFormat = renderingFormat; 40 40 } 41 41 42 public Class<? extends RenderingFormat<? >> getRenderingFormat() {42 public Class<? extends RenderingFormat<? extends CharSequence>> getRenderingFormat() { 43 43 return _renderingFormat; 44 44 } -
AnalyzerBeans/trunk/cli/src/main/java/org/eobjects/analyzer/cli/CliRunner.java
r3068 r3070 24 24 import java.io.File; 25 25 import java.io.FileInputStream; 26 import java.io.FileNotFoundException; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 26 29 import java.io.InputStream; 30 import java.io.OutputStream; 27 31 import java.io.PrintWriter; 32 import java.io.StringWriter; 33 import java.io.Writer; 28 34 import java.util.Collection; 29 35 import java.util.List; … … 32 38 import java.util.Set; 33 39 40 import org.apache.commons.lang.SerializationUtils; 34 41 import org.eobjects.analyzer.beans.api.Renderer; 42 import org.eobjects.analyzer.beans.api.RenderingFormat; 35 43 import org.eobjects.analyzer.configuration.AnalyzerBeansConfiguration; 36 44 import org.eobjects.analyzer.configuration.JaxbConfigurationReader; 45 import org.eobjects.analyzer.connection.Datastore; 37 46 import org.eobjects.analyzer.connection.DatastoreConnection; 38 import org.eobjects.analyzer.connection.Datastore;39 47 import org.eobjects.analyzer.data.DataTypeFamily; 40 48 import org.eobjects.analyzer.descriptors.AnalyzerBeanDescriptor; … … 51 59 import org.eobjects.analyzer.job.runner.AnalysisRunnerImpl; 52 60 import org.eobjects.analyzer.result.AnalyzerResult; 61 import org.eobjects.analyzer.result.SimpleAnalysisResult; 53 62 import org.eobjects.analyzer.result.renderer.RendererFactory; 54 import org.eobjects.analyzer.result.renderer.TextRenderingFormat;55 63 import org.eobjects.metamodel.DataContext; 56 64 import org.eobjects.metamodel.schema.Schema; 57 65 import org.eobjects.metamodel.schema.Table; 58 66 import org.eobjects.metamodel.util.FileHelper; 67 import org.eobjects.metamodel.util.ImmutableRef; 68 import org.eobjects.metamodel.util.LazyRef; 69 import org.eobjects.metamodel.util.Ref; 59 70 import org.slf4j.Logger; 60 71 import org.slf4j.LoggerFactory; … … 70 81 71 82 private final CliArguments _arguments; 72 private final PrintWriter _out; 83 private final Ref<OutputStream> _outputStreamRef; 84 private final Ref<Writer> _writerRef; 73 85 private final boolean _closeOut; 74 86 … … 79 91 * 80 92 * @param arguments 81 * @param out 93 * @param writer 94 * @param outputStream 82 95 */ 83 protected CliRunner(CliArguments arguments, PrintWriter out) {96 protected CliRunner(CliArguments arguments, Writer writer, OutputStream outputStream) { 84 97 _arguments = arguments; 85 if (out == null) {98 if (outputStream == null) { 86 99 final File outputFile = arguments.getOutputFile(); 87 100 if (outputFile == null) { 88 _out = new PrintWriter(System.out); 89 } else { 90 _out = new PrintWriter(FileHelper.getWriter(outputFile)); 101 _outputStreamRef = null; 102 _writerRef = new LazyRef<Writer>() { 103 @Override 104 protected Writer fetch() { 105 return new PrintWriter(System.out); 106 } 107 }; 108 } else { 109 _writerRef = new LazyRef<Writer>() { 110 @Override 111 protected Writer fetch() { 112 return FileHelper.getWriter(outputFile); 113 } 114 }; 115 _outputStreamRef = new LazyRef<OutputStream>() { 116 @Override 117 protected OutputStream fetch() { 118 try { 119 return new FileOutputStream(outputFile); 120 } catch (FileNotFoundException e) { 121 throw new IllegalStateException(e); 122 } 123 } 124 }; 91 125 } 92 126 _closeOut = true; 93 127 } else { 94 _out = out; 128 _writerRef = new ImmutableRef<Writer>(writer); 129 _outputStreamRef = new ImmutableRef<OutputStream>(outputStream); 95 130 _closeOut = false; 96 131 } … … 98 133 99 134 public CliRunner(CliArguments arguments) { 100 this(arguments, null );135 this(arguments, null, null); 101 136 } 102 137 … … 180 215 Table table = schema.getTableByName(tableName); 181 216 if (table == null) { 182 _out.println("No such table: " + tableName);217 write("No such table: " + tableName); 183 218 } else { 184 219 String[] columnNames = table.getColumnNames(); 185 _out.println("Columns:");186 _out.println("--------");220 write("Columns:"); 221 write("--------"); 187 222 for (String columnName : columnNames) { 188 _out.println(columnName);223 write(columnName); 189 224 } 190 225 } … … 221 256 System.err.println("No tables in schema!"); 222 257 } else { 223 _out.println("Tables:");224 _out.println("-------");258 write("Tables:"); 259 write("-------"); 225 260 for (String tableName : tableNames) { 226 _out.println(tableName);261 write(tableName); 227 262 } 228 263 } … … 246 281 String[] schemaNames = con.getDataContext().getSchemaNames(); 247 282 if (schemaNames == null || schemaNames.length == 0) { 248 _out.println("No schemas in datastore!");283 write("No schemas in datastore!"); 249 284 } else { 250 _out.println("Schemas:");251 _out.println("--------");285 write("Schemas:"); 286 write("--------"); 252 287 for (String schemaName : schemaNames) { 253 _out.println(schemaName);288 write(schemaName); 254 289 } 255 290 } … … 262 297 String[] datastoreNames = configuration.getDatastoreCatalog().getDatastoreNames(); 263 298 if (datastoreNames == null || datastoreNames.length == 0) { 264 _out.println("No datastores configured!");265 } else { 266 _out.println("Datastores:");267 _out.println("-----------");299 write("No datastores configured!"); 300 } else { 301 write("Datastores:"); 302 write("-----------"); 268 303 for (String datastoreName : datastoreNames) { 269 _out.println(datastoreName);304 write(datastoreName); 270 305 } 271 306 } … … 287 322 288 323 if (resultFuture.isSuccessful()) { 289 _out.println("SUCCESS!"); 290 final Set<Entry<ComponentJob, AnalyzerResult>> results = resultFuture.getResultMap().entrySet(); 291 292 final RendererFactory rendererFactory = new RendererFactory(configuration.getDescriptorProvider(), null); 293 294 for (Entry<ComponentJob, AnalyzerResult> result : results) { 295 final ComponentJob componentJob = result.getKey(); 296 final AnalyzerResult analyzerResult = result.getValue(); 297 String name = componentJob.getName(); 298 if (name == null) { 299 name = componentJob.toString(); 300 } 301 302 _out.println("\nRESULT: " + name); 303 304 Renderer<? super AnalyzerResult, ? extends CharSequence> renderer = rendererFactory.getRenderer( 305 analyzerResult, TextRenderingFormat.class); 306 CharSequence renderedResult = renderer.render(analyzerResult); 307 308 _out.println(renderedResult); 309 } 310 } else { 311 _out.println("ERROR!"); 312 _out.println("------"); 324 final CliOutputType outputType = _arguments.getOutputType(); 325 if (outputType == CliOutputType.SERIALIZED) { 326 327 SimpleAnalysisResult result = new SimpleAnalysisResult(resultFuture.getResultMap()); 328 SerializationUtils.serialize(result, _outputStreamRef.get()); 329 330 } else { 331 final Class<? extends RenderingFormat<? extends CharSequence>> renderingFormat = outputType 332 .getRenderingFormat(); 333 final Set<Entry<ComponentJob, AnalyzerResult>> results = resultFuture.getResultMap().entrySet(); 334 final RendererFactory rendererFactory = new RendererFactory(configuration.getDescriptorProvider(), null); 335 336 if (outputType == CliOutputType.TEXT) { 337 write("SUCCESS!"); 338 } else if (outputType == CliOutputType.HTML) { 339 write("<html><body>"); 340 write("<div class\"analysisResultContainer\">"); 341 write("<h1 class=\"analysisResultHeader\">Success!</h1>"); 342 } 343 344 for (Entry<ComponentJob, AnalyzerResult> result : results) { 345 final ComponentJob componentJob = result.getKey(); 346 final AnalyzerResult analyzerResult = result.getValue(); 347 String name = componentJob.getName(); 348 if (name == null) { 349 name = componentJob.toString(); 350 } 351 352 if (outputType == CliOutputType.TEXT) { 353 write("\nRESULT: " + name); 354 } else if (outputType == CliOutputType.HTML) { 355 write("<div=\"analyzerResultContainer\"><h2 class=\"analyzerResultHeader\">Result: " + name 356 + "</h2><div class=\"analyzerResultPanel\">"); 357 } 358 359 Renderer<? super AnalyzerResult, ? extends CharSequence> renderer = rendererFactory.getRenderer( 360 analyzerResult, renderingFormat); 361 if (renderer == null) { 362 writeMissingRendererError(analyzerResult, outputType); 363 } else { 364 CharSequence renderedResult = renderer.render(analyzerResult); 365 write(renderedResult.toString()); 366 } 367 368 if (outputType == CliOutputType.HTML) { 369 write("</div></div>"); 370 } 371 } 372 373 if (outputType == CliOutputType.HTML) { 374 write("</div>"); 375 write("</body></html>"); 376 } 377 } 378 } else { 379 write("ERROR!"); 380 write("------"); 313 381 314 382 List<Throwable> errors = resultFuture.getErrors(); 315 _out.println(errors.size() + " error(s) occurred while executing the job:");383 write(errors.size() + " error(s) occurred while executing the job:"); 316 384 317 385 for (Throwable throwable : errors) { 318 _out.println("------"); 319 throwable.printStackTrace(_out); 386 write("------"); 387 StringWriter stringWriter = new StringWriter(); 388 throwable.printStackTrace(new PrintWriter(stringWriter)); 389 write(stringWriter.toString()); 320 390 } 321 391 322 392 throw errors.get(0); 393 } 394 } 395 396 private void writeMissingRendererError(AnalyzerResult analyzerResult, CliOutputType outputType) { 397 // we only expect HTML here, since TEXT rendering has a default renderer 398 // for all, and SERIALIZED rendering will have a different loop of 399 // storing the result. 400 assert outputType == CliOutputType.HTML; 401 402 if (outputType == CliOutputType.HTML) { 403 write("<h3 class=\"analyzerResultNoRendererHeader\">Error: Could not find appropriate renderer for result</h3>"); 404 write("<pre class=\"analyzerResultNoRendererBody\">" + analyzerResult.toString() + "</pre>"); 323 405 } 324 406 } … … 328 410 .getAnalyzerBeanDescriptors(); 329 411 if (descriptors == null || descriptors.isEmpty()) { 330 _out.println("No analyzers configured!");331 } else { 332 _out.println("Analyzers:");333 _out.println("----------");412 write("No analyzers configured!"); 413 } else { 414 write("Analyzers:"); 415 write("----------"); 334 416 printBeanDescriptors(descriptors); 335 417 } … … 340 422 .getTransformerBeanDescriptors(); 341 423 if (descriptors == null || descriptors.isEmpty()) { 342 _out.println("No transformers configured!");343 } else { 344 _out.println("Transformers:");345 _out.println("-------------");424 write("No transformers configured!"); 425 } else { 426 write("Transformers:"); 427 write("-------------"); 346 428 printBeanDescriptors(descriptors); 347 429 } … … 352 434 .getFilterBeanDescriptors(); 353 435 if (descriptors == null || descriptors.isEmpty()) { 354 _out.println("No filters configured!");355 } else { 356 _out.println("Filters:");357 _out.println("--------");436 write("No filters configured!"); 437 } else { 438 write("Filters:"); 439 write("--------"); 358 440 printBeanDescriptors(descriptors); 359 441 } … … 364 446 .getExplorerBeanDescriptors(); 365 447 if (descriptors == null || descriptors.isEmpty()) { 366 _out.println("No explorers configured!");367 } else { 368 _out.println("Explorers:");369 _out.println("----------");448 write("No explorers configured!"); 449 } else { 450 write("Explorers:"); 451 write("----------"); 370 452 printBeanDescriptors(descriptors); 371 453 } … … 375 457 logger.debug("Printing {} descriptors", descriptors.size()); 376 458 for (BeanDescriptor<?> descriptor : descriptors) { 377 _out.println("name: " + descriptor.getDisplayName());459 write("name: " + descriptor.getDisplayName()); 378 460 379 461 Set<ConfiguredPropertyDescriptor> propertiesForInput = descriptor.getConfiguredPropertiesForInput(); … … 382 464 if (propertyForInput != null) { 383 465 if (propertyForInput.isArray()) { 384 _out.println(" - Consumes multiple input columns (type: " 466 write(" - Consumes multiple input columns (type: " + propertyForInput.getInputColumnDataTypeFamily() 467 + ")"); 468 } else { 469 write(" - Consumes a single input column (type: " + propertyForInput.getInputColumnDataTypeFamily() 470 + ")"); 471 } 472 } 473 } else { 474 write(" - Consumes " + propertiesForInput.size() + " named inputs"); 475 for (ConfiguredPropertyDescriptor propertyForInput : propertiesForInput) { 476 if (propertyForInput.isArray()) { 477 write(" Input columns: " + propertyForInput.getName() + " (type: " 385 478 + propertyForInput.getInputColumnDataTypeFamily() + ")"); 386 479 } else { 387 _out.println(" - Consumes a single input column (type: " 388 + propertyForInput.getInputColumnDataTypeFamily() + ")"); 389 } 390 } 391 } else { 392 _out.println(" - Consumes " + propertiesForInput.size() + " named inputs"); 393 for (ConfiguredPropertyDescriptor propertyForInput : propertiesForInput) { 394 if (propertyForInput.isArray()) { 395 _out.println(" Input columns: " + propertyForInput.getName() + " (type: " 396 + propertyForInput.getInputColumnDataTypeFamily() + ")"); 397 } else { 398 _out.println(" Input column: " + propertyForInput.getName() + " (type: " 480 write(" Input column: " + propertyForInput.getName() + " (type: " 399 481 + propertyForInput.getInputColumnDataTypeFamily() + ")"); 400 482 } … … 405 487 for (ConfiguredPropertyDescriptor property : properties) { 406 488 if (!property.isInputColumn()) { 407 _out.println(" - Property: name=" + property.getName() + ", type="408 + property.getBaseType().getSimpleName() +", required=" + property.isRequired());489 write(" - Property: name=" + property.getName() + ", type=" + property.getBaseType().getSimpleName() 490 + ", required=" + property.isRequired()); 409 491 } 410 492 } … … 412 494 if (descriptor instanceof TransformerBeanDescriptor<?>) { 413 495 DataTypeFamily dataTypeFamily = ((TransformerBeanDescriptor<?>) descriptor).getOutputDataTypeFamily(); 414 _out.println(" - Output type is: " + dataTypeFamily);496 write(" - Output type is: " + dataTypeFamily); 415 497 } 416 498 … … 418 500 Set<String> categoryNames = ((FilterBeanDescriptor<?, ?>) descriptor).getOutcomeCategoryNames(); 419 501 for (String categoryName : categoryNames) { 420 _out.println(" - Outcome category: " + categoryName); 421 } 422 } 502 write(" - Outcome category: " + categoryName); 503 } 504 } 505 } 506 } 507 508 private void write(String str) { 509 try { 510 _writerRef.get().write(str + "\n"); 511 } catch (IOException e) { 512 throw new IllegalStateException(e); 423 513 } 424 514 } … … 427 517 public void close() { 428 518 if (_closeOut) { 429 FileHelper.safeClose(_out); 519 close(_writerRef); 520 close(_outputStreamRef); 521 } 522 } 523 524 private void close(Ref<?> ref) { 525 if (ref != null) { 526 if (ref instanceof LazyRef) { 527 LazyRef<?> lazyRef = (LazyRef<?>) ref; 528 if (lazyRef.isFetched()) { 529 FileHelper.safeClose(ref.get()); 530 } 531 } else { 532 FileHelper.safeClose(ref.get()); 533 } 430 534 } 431 535 } -
AnalyzerBeans/trunk/cli/src/test/java/org/eobjects/analyzer/cli/MainTest.java
r3068 r3070 20 20 package org.eobjects.analyzer.cli; 21 21 22 import java.io.File; 23 import java.io.FileInputStream; 22 24 import java.io.IOException; 23 25 import java.io.OutputStream; … … 27 29 import junit.framework.TestCase; 28 30 31 import org.apache.commons.lang.SerializationUtils; 29 32 import org.apache.log4j.PropertyConfigurator; 33 import org.eobjects.analyzer.result.AnalysisResult; 34 import org.eobjects.metamodel.util.FileHelper; 30 35 31 36 public class MainTest extends TestCase { … … 39 44 _originalSysOut = System.out; 40 45 useAsSystemOut(_stringWriter); 41 46 42 47 PropertyConfigurator.configure("src/test/resources/log4j.xml"); 43 48 } … … 194 199 assertEquals("SUCCESS!", lines[0]); 195 200 } 201 202 public void testWriteToFile() throws Throwable { 203 String filename = "target/test_write_to_file.txt"; 204 Main.main(("-conf examples/conf.xml -job examples/employees_job.xml -of " + filename).split(" ")); 205 206 File file = new File(filename); 207 assertTrue(file.exists()); 208 String result = FileHelper.readFileAsString(file); 209 assertEquals("SUCCESS!", result.split("\n")[0].trim()); 210 211 assertEquals("", _stringWriter.toString()); 212 } 213 214 public void testWriteHtmlToFile() throws Throwable { 215 String filename = "target/test_write_html_to_file.html"; 216 Main.main(("-conf examples/conf.xml -job examples/employees_job.xml -of " + filename + " -ot HTML").split(" ")); 217 218 File file = new File(filename); 219 assertTrue(file.exists()); 220 String result = FileHelper.readFileAsString(file); 221 String[] lines = result.split("\n"); 222 223 assertEquals("<html><body>", lines[0]); 224 } 225 226 public void testWriteSerializedToFile() throws Throwable { 227 String filename = "target/test_write_serialized_to_file.analysis.result.dat"; 228 Main.main(("-conf examples/conf.xml -job examples/employees_job.xml -of " + filename + " -ot SERIALIZED").split(" ")); 229 230 File file = new File(filename); 231 assertTrue(file.exists()); 232 233 AnalysisResult result = (AnalysisResult) SerializationUtils.deserialize(new FileInputStream(file)); 234 assertNotNull(result); 235 assertEquals(6, result.getResults().size()); 236 } 196 237 } -
AnalyzerBeans/trunk/core/src/main/java/org/eobjects/analyzer/job/concurrent/JobCompletionTaskListener.java
r3040 r3070 20 20 package org.eobjects.analyzer.job.concurrent; 21 21 22 import java.util.Date; 22 23 import java.util.concurrent.CountDownLatch; 23 24 import java.util.concurrent.TimeUnit; … … 42 43 private final AnalysisListener _analysisListener; 43 44 private final AnalysisJobMetrics _analysisJobMetrics; 45 private Date _completionTime; 44 46 45 47 public JobCompletionTaskListener(AnalysisJobMetrics analysisJobMetrics, AnalysisListener analysisListener, … … 72 74 _countDownLatch.countDown(); 73 75 if (_countDownLatch.getCount() == 0) { 76 _completionTime = new Date(); 74 77 _analysisListener.jobSuccess(_analysisJobMetrics.getAnalysisJob(), _analysisJobMetrics); 75 78 } … … 82 85 _countDownLatch.countDown(); 83 86 } 87 88 @Override 89 public Date getCompletionTime() { 90 return _completionTime; 91 } 84 92 } -
AnalyzerBeans/trunk/core/src/main/java/org/eobjects/analyzer/job/concurrent/StatusAwareTaskListener.java
r1454 r3070 20 20 package org.eobjects.analyzer.job.concurrent; 21 21 22 import java.util.Date; 22 23 import java.util.concurrent.TimeUnit; 23 24 … … 35 36 36 37 public void await(long timeout, TimeUnit timeUnit) throws InterruptedException; 38 39 public Date getCompletionTime(); 37 40 } -
AnalyzerBeans/trunk/core/src/main/java/org/eobjects/analyzer/job/runner/AnalysisResultFutureImpl.java
r2631 r3070 21 21 22 22 import java.util.ArrayList; 23 import java.util.Date; 23 24 import java.util.HashMap; 24 25 import java.util.List; … … 56 57 } 57 58 return _done; 59 } 60 61 @Override 62 public Date getCreationDate() { 63 return _jobTaskListener.getCompletionTime(); 58 64 } 59 65 -
AnalyzerBeans/trunk/core/src/main/java/org/eobjects/analyzer/result/AnalysisResult.java
r3031 r3070 20 20 package org.eobjects.analyzer.result; 21 21 22 import java.util.Date; 22 23 import java.util.List; 23 24 import java.util.Map; … … 57 58 */ 58 59 public Map<ComponentJob, AnalyzerResult> getResultMap(); 60 61 /** 62 * Gets the time that the results were created 63 * 64 * @return the time that the results were created 65 */ 66 public Date getCreationDate(); 59 67 } -
AnalyzerBeans/trunk/core/src/main/java/org/eobjects/analyzer/result/SimpleAnalysisResult.java
r3031 r3070 23 23 import java.util.ArrayList; 24 24 import java.util.Collections; 25 import java.util.Date; 25 26 import java.util.List; 26 27 import java.util.Map; … … 40 41 41 42 private final Map<ComponentJob, AnalyzerResult> _results; 43 private final Date _creationDate; 42 44 43 45 public SimpleAnalysisResult(Map<ComponentJob, AnalyzerResult> results) { 44 46 _results = results; 47 _creationDate = new Date(); 45 48 } 46 49 … … 60 63 } 61 64 65 @Override 66 public Date getCreationDate() { 67 return _creationDate; 68 } 69 62 70 } -
AnalyzerBeans/trunk/core/src/main/java/org/eobjects/analyzer/result/renderer/RendererFactory.java
r2482 r3070 92 92 */ 93 93 public <I extends Renderable, O> Renderer<? super I, ? extends O> getRenderer(I renderable, 94 Class<? extends RenderingFormat< O>> renderingFormat) {94 Class<? extends RenderingFormat<? extends O>> renderingFormat) { 95 95 96 96 RendererSelection bestMatch = null;
Note: See TracChangeset
for help on using the changeset viewer.
