Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.concurrent; 027 028import java.util.Timer; 029import java.util.TimerTask; 030import javafx.beans.property.BooleanProperty; 031import javafx.beans.property.IntegerProperty; 032import javafx.beans.property.ObjectProperty; 033import javafx.beans.property.ReadOnlyIntegerProperty; 034import javafx.beans.property.ReadOnlyIntegerWrapper; 035import javafx.beans.property.ReadOnlyObjectProperty; 036import javafx.beans.property.ReadOnlyObjectWrapper; 037import javafx.beans.property.SimpleBooleanProperty; 038import javafx.beans.property.SimpleIntegerProperty; 039import javafx.beans.property.SimpleObjectProperty; 040import javafx.util.Callback; 041import javafx.util.Duration; 042 043/** 044 * <p>The ScheduledService is a {@link Service} which will automatically restart 045 * itself after a successful execution, and under some conditions will 046 * restart even in case of failure. A new ScheduledService begins in 047 * the READY state, just as a normal Service. After calling 048 * <code>start</code> or <code>restart</code>, the ScheduledService will 049 * enter the SCHEDULED state for the duration specified by <code>delay</code>. 050 * </p> 051 * 052 * <p>Once RUNNING, the ScheduledService will execute its Task. On successful 053 * completion, the ScheduledService will transition to the SUCCEEDED state, 054 * and then to the READY state and back to the SCHEDULED state. The amount 055 * of time the ScheduledService will remain in this state depends on the 056 * amount of time between the last state transition to RUNNING, and the 057 * current time, and the <code>period</code>. In short, the <code>period</code> 058 * defines the minimum amount of time from the start of one run and the start of 059 * the next. If the previous execution completed before <code>period</code> expires, 060 * then the ScheduledService will remain in the SCHEDULED state until the period 061 * expires. If on the other hand the execution took longer than the 062 * specified period, then the ScheduledService will immediately transition 063 * back to RUNNING. </p> 064 * 065 * <p>If, while RUNNING, the ScheduledService's Task throws an error or in 066 * some other way ends up transitioning to FAILED, then the ScheduledService 067 * will either restart or quit, depending on the values for 068 * <code>backoffStrategy</code>, <code>restartOnFailure</code>, and 069 * <code>maximumFailureCount</code>.</p> 070 * 071 * <p>If a failure occurs and <code>restartOnFailure</code> is false, then 072 * the ScheduledService will transition to FAILED and will stop. To restart 073 * a failed ScheduledService, you must call restart manually.</p> 074 * 075 * <p>If a failure occurs and <code>restartOnFailure</code> is true, then 076 * the the ScheduledService <em>may</em> restart automatically. First, 077 * the result of calling <code>backoffStrategy</code> will become the 078 * new <code>cumulativePeriod</code>. In this way, after each failure, you can cause 079 * the service to wait a longer and longer period of time before restarting. 080 * Once the task completes successfully, the cumulativePeriod is reset to 081 * the value of <code>period</code>.</p> 082 * 083 * <p>ScheduledService defines static EXPONENTIAL_BACKOFF_STRATEGY and LOGARITHMIC_BACKOFF_STRATEGY 084 * implementations, of which LOGARITHMIC_BACKOFF_STRATEGY is the default value for 085 * backoffStrategy. After <code>maximumFailureCount</code> is reached, the 086 * ScheduledService will transition to FAILED in exactly the same way as if 087 * <code>restartOnFailure</code> were false.</p> 088 * 089 * <p>If the <code>period</code> or <code>delay</code> is changed while the 090 * ScheduledService is running, the new values will be taken into account on the 091 * next iteration. For example, if the <code>period</code> is increased, then the next time the 092 * ScheduledService enters the SCHEDULED state, the new <code>period</code> will be used. 093 * Likewise, if the <code>delay</code> is changed, the new value will be honored on 094 * the next restart or reset/start.</p> 095 * 096 * The ScheduledService is typically used for use cases that involve polling. For 097 * example, you may want to ping a server on a regular basis to see if there are 098 * any updates. Such as ScheduledService might be implemented like this: 099 * 100 * <pre><code> 101 * ScheduledService<Document> svc = new ScheduledService<>(Duration.seconds(1)) { 102 * protected Task<Document> createTask() { 103 * return new Task<Document>() { 104 * protected Document call() { 105 * // Connect to a Server 106 * // Get the XML document 107 * // Parse it into a document 108 * return document; 109 * } 110 * } 111 * } 112 * } 113 * </code></pre> 114 * 115 * This example will ping the remote server every 1 second. 116 * 117 * <p>Timing for this class is not absolutely reliable. A very busy event thread might introduce some timing 118 * lag into the beginning of the execution of the background Task, so very small values for the period or 119 * delay are likely to be inaccurate. A delay or period in the hundreds of milliseconds or larger should be 120 * fairly reliable.</p> 121 * 122 * <p>The ScheduledService in its default configuration has a default <code>period</code> of 0 and a 123 * default <code>delay</code> of 0. This will cause the ScheduledService to execute the task immediately 124 * upon {@link #start()}, and re-executing immediately upon successful completion.</p> 125 * 126 * <p>For this purposes of this class, any Duration that answers true to {@link javafx.util.Duration#isUnknown()} 127 * will treat that duration as if it were Duration.ZERO. Likewise, any Duration which answers true 128 * to {@link javafx.util.Duration#isIndefinite()} will be treated as if it were a duration of Double.MAX_VALUE 129 * milliseconds. Any null Duration is treated as Duration.ZERO. Any custom implementation of an backoff strategy 130 * callback must be prepared to handle these different potential values.</p> 131 * 132 * <p>The ScheduledService introduces a new property called {@link #lastValue}. The lastValue is the value that 133 * was last successfully computed. Because a Service clears its {@code value} property on each run, and 134 * because the ScheduledService will reschedule a run immediately after completion (unless it enters the 135 * cancelled or failed states), the value property is not overly useful on a ScheduledService. In most cases 136 * you will want to instead use the value returned by lastValue.</p> 137 * 138 * <b>Implementer Note:</b> The {@link #ready()}, {@link #scheduled()}, {@link #running()}, {@link #succeeded()}, 139 * {@link #cancelled()}, and {@link #failed()} methods are implemented in this class. Subclasses which also 140 * override these methods must take care to invoke the super implementation. 141 * 142 * @param <V> The computed value of the ScheduledService 143 * @since 8 144 */ 145public abstract class ScheduledService<V> extends Service<V> { 146 /** 147 * A Callback implementation for the <code>backoffStrategy</code> property which 148 * will exponentially backoff the period between re-executions in the case of 149 * a failure. This computation takes the original period and the number of 150 * consecutive failures and computes the backoff amount from that information. 151 * 152 * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then 153 * the result of this method will simply be {@code Math.exp(currentFailureCount)}. In all other cases, 154 * the returned value is the same as {@code period + (period * Math.exp(currentFailureCount))).</p> 155 */ 156 public static final Callback<ScheduledService<?>, Duration> EXPONENTIAL_BACKOFF_STRATEGY 157 = new Callback<ScheduledService<?>, Duration>() { 158 @Override public Duration call(ScheduledService<?> service) { 159 if (service == null) return Duration.ZERO; 160 final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis(); 161 final double x = service.getCurrentFailureCount(); 162 return Duration.millis(period == 0 ? Math.exp(x) : period + (period * Math.exp(x))); 163 } 164 }; 165 166 /** 167 * A Callback implementation for the <code>backoffStrategy</code> property which 168 * will logarithmically backoff the period between re-executions in the case of 169 * a failure. This computation takes the original period and the number of 170 * consecutive failures and computes the backoff amount from that information. 171 * 172 * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then 173 * the result of this method will simply be {@code Math.log1p(currentFailureCount)}. In all other cases, 174 * the returned value is the same as {@code period + (period * Math.log1p(currentFailureCount))).</p> 175 */ 176 public static final Callback<ScheduledService<?>, Duration> LOGARITHMIC_BACKOFF_STRATEGY 177 = new Callback<ScheduledService<?>, Duration>() { 178 @Override public Duration call(ScheduledService<?> service) { 179 if (service == null) return Duration.ZERO; 180 final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis(); 181 final double x = service.getCurrentFailureCount(); 182 return Duration.millis(period == 0 ? Math.log1p(x) : period + (period * Math.log1p(x))); 183 } 184 }; 185 186 /** 187 * A Callback implementation for the <code>backoffStrategy</code> property which 188 * will linearly backoff the period between re-executions in the case of 189 * a failure. This computation takes the original period and the number of 190 * consecutive failures and computes the backoff amount from that information. 191 * 192 * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then 193 * the result of this method will simply be {@code currentFailureCount}. In all other cases, 194 * the returned value is the same as {@code period + (period * currentFailureCount).</p> 195 */ 196 public static final Callback<ScheduledService<?>, Duration> LINEAR_BACKOFF_STRATEGY 197 = new Callback<ScheduledService<?>, Duration>() { 198 @Override public Duration call(ScheduledService<?> service) { 199 if (service == null) return Duration.ZERO; 200 final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis(); 201 final double x = service.getCurrentFailureCount(); 202 return Duration.millis(period == 0 ? x : period + (period * x)); 203 } 204 }; 205 206 /** 207 * This Timer is used to schedule the delays for each ScheduledService. A single timer 208 * ought to be able to easily service thousands of ScheduledService objects. 209 */ 210 private static final Timer DELAY_TIMER = new Timer("ScheduledService Delay Timer", true); 211 212 /** 213 * The initial delay between when the ScheduledService is first started, and when it will begin 214 * operation. This is the amount of time the ScheduledService will remain in the SCHEDULED state, 215 * before entering the RUNNING state, following a fresh invocation of {@link #start()} or {@link #restart()}. 216 */ 217 private ObjectProperty<Duration> delay = new SimpleObjectProperty<>(this, "delay", Duration.ZERO); 218 public final Duration getDelay() { return delay.get(); } 219 public final void setDelay(Duration value) { delay.set(value); } 220 public final ObjectProperty<Duration> delayProperty() { return delay; } 221 222 /** 223 * The minimum amount of time to allow between the start of the last run and the start of the next run. 224 * The actual period (also known as <code>cumulativePeriod</code>) 225 * will depend on this property as well as the <code>backoffStrategy</code> and number of failures. 226 */ 227 private ObjectProperty<Duration> period = new SimpleObjectProperty<>(this, "period", Duration.ZERO); 228 public final Duration getPeriod() { return period.get(); } 229 public final void setPeriod(Duration value) { period.set(value); } 230 public final ObjectProperty<Duration> periodProperty() { return period; } 231 232 /** 233 * Computes the amount of time to add to the period on each failure. This cumulative amount is reset whenever 234 * the the ScheduledService is manually restarted. 235 */ 236 private ObjectProperty<Callback<ScheduledService<?>,Duration>> backoffStrategy = 237 new SimpleObjectProperty<>(this, "backoffStrategy", LOGARITHMIC_BACKOFF_STRATEGY); 238 public final Callback<ScheduledService<?>,Duration> getBackoffStrategy() { return backoffStrategy.get(); } 239 public final void setBackoffStrategy(Callback<ScheduledService<?>, Duration> value) { backoffStrategy.set(value); } 240 public final ObjectProperty<Callback<ScheduledService<?>,Duration>> backoffStrategyProperty() { return backoffStrategy; } 241 242 /** 243 * Indicates whether the ScheduledService should automatically restart in the case of a failure in the Task. 244 */ 245 private BooleanProperty restartOnFailure = new SimpleBooleanProperty(this, "restartOnFailure", true); 246 public final boolean getRestartOnFailure() { return restartOnFailure.get(); } 247 public final void setRestartOnFailure(boolean value) { restartOnFailure.set(value); } 248 public final BooleanProperty restartOnFailureProperty() { return restartOnFailure; } 249 250 /** 251 * The maximum number of times the ScheduledService can fail before it simply ends in the FAILED 252 * state. You can of course restart the ScheduledService manually, which will cause the current 253 * count to be reset. 254 */ 255 private IntegerProperty maximumFailureCount = new SimpleIntegerProperty(this, "maximumFailureCount", Integer.MAX_VALUE); 256 public final int getMaximumFailureCount() { return maximumFailureCount.get(); } 257 public final void setMaximumFailureCount(int value) { maximumFailureCount.set(value); } 258 public final IntegerProperty maximumFailureCountProperty() { return maximumFailureCount; } 259 260 /** 261 * The current number of times the ScheduledService has failed. This is reset whenever the 262 * ScheduledService is manually restarted. 263 */ 264 private ReadOnlyIntegerWrapper currentFailureCount = new ReadOnlyIntegerWrapper(this, "currentFailureCount", 0); 265 public final int getCurrentFailureCount() { return currentFailureCount.get(); } 266 public final ReadOnlyIntegerProperty currentFailureCountProperty() { return currentFailureCount.getReadOnlyProperty(); } 267 private void setCurrentFailureCount(int value) { 268 currentFailureCount.set(value); 269 } 270 271 /** 272 * The current cumulative period in use between iterations. This will be the same as <code>period</code>, 273 * except after a failure, in which case the result of the backoffStrategy will be used as the cumulative period 274 * following each failure. This is reset whenever the ScheduledService is manually restarted or an iteration 275 * is successful. The cumulativePeriod is modified when the ScheduledService enters the scheduled state. 276 * The cumulativePeriod can be capped by setting the {@code maximumCumulativePeriod}. 277 */ 278 private ReadOnlyObjectWrapper<Duration> cumulativePeriod = new ReadOnlyObjectWrapper<>(this, "cumulativePeriod", Duration.ZERO); 279 public final Duration getCumulativePeriod() { return cumulativePeriod.get(); } 280 public final ReadOnlyObjectProperty<Duration> cumulativePeriodProperty() { return cumulativePeriod.getReadOnlyProperty(); } 281 void setCumulativePeriod(Duration value) { // package private for testing 282 // Make sure any null value is turned into ZERO 283 Duration newValue = value == null || value.toMillis() < 0 ? Duration.ZERO : value; 284 // Cap the newValue based on the maximumCumulativePeriod. 285 Duration maxPeriod = maximumCumulativePeriod.get(); 286 if (maxPeriod != null && !maxPeriod.isUnknown() && !newValue.isUnknown()) { 287 if (maxPeriod.toMillis() < 0) { 288 newValue = Duration.ZERO; 289 } else if (!maxPeriod.isIndefinite() && newValue.greaterThan(maxPeriod)) { 290 newValue = maxPeriod; 291 } 292 } 293 cumulativePeriod.set(newValue); 294 } 295 296 /** 297 * The maximum allowed value for the cumulativePeriod. Setting this value will help ensure that in the case of 298 * repeated failures the back-off algorithm doesn't end up producing unreasonably large values for 299 * cumulative period. The cumulative period is guaranteed not to be any larger than this value. If the 300 * maximumCumulativePeriod is negative, then cumulativePeriod will be capped at 0. If maximumCumulativePeriod 301 * is NaN or null, then it will not influence the cumulativePeriod. 302 */ 303 private ObjectProperty<Duration> maximumCumulativePeriod = new SimpleObjectProperty<>(this, "maximumCumulativePeriod", Duration.INDEFINITE); 304 public final Duration getMaximumCumulativePeriod() { return maximumCumulativePeriod.get(); } 305 public final void setMaximumCumulativePeriod(Duration value) { maximumCumulativePeriod.set(value); } 306 public final ObjectProperty<Duration> maximumCumulativePeriodProperty() { return maximumCumulativePeriod; } 307 308 /** 309 * The last successfully computed value. During each iteration, the "value" of the ScheduledService will be 310 * reset to null, as with any other Service. The "lastValue" however will be set to the most recently 311 * successfully computed value, even across iterations. It is reset however whenever you manually call 312 * reset or restart. 313 */ 314 private ReadOnlyObjectWrapper<V> lastValue = new ReadOnlyObjectWrapper<>(this, "lastValue", null); 315 public final V getLastValue() { return lastValue.get(); } 316 public final ReadOnlyObjectProperty lastValueProperty() { return lastValue.getReadOnlyProperty(); } 317 318 /** 319 * The timestamp of the last time the task was run. This is used to compute the amount 320 * of delay between successive iterations by taking the cumulativePeriod into account. 321 */ 322 private long lastRunTime = 0L; 323 324 /** 325 * Whether or not this iteration is a "fresh start", such as the initial call to start, 326 * or a call to restart, or a call to reset followed by a call to start. 327 */ 328 private boolean freshStart = true; 329 330 /** 331 * This is a TimerTask scheduled with the DELAY_TIMER. All it does is kick off the execution 332 * of the actual background Task. 333 */ 334 private TimerTask delayTask = null; 335 336 // This method is invoked by Service to actually execute the task. In the normal implementation 337 // in Service, this method will simply delegate to the Executor. In ScheduledService, however, 338 // we instead will delay the correct amount of time before we finally invoke executeTaskNow, 339 // which is where we end up delegating to the executor. 340 @Override protected void executeTask(final Task<V> task) { 341 assert task != null; 342 checkThread(); 343 344 if (freshStart) { 345 // The delayTask should have concluded and been made null by this point. 346 // If not, then somehow we were paused waiting for another iteration and 347 // somebody caused the system to run again. However resetting things should 348 // have cleared the delayTask. 349 assert delayTask == null; 350 351 // The cumulativePeriod needs to be initialized 352 setCumulativePeriod(getPeriod()); 353 354 // Pause for the "delay" amount of time and then execute 355 final long d = (long) normalize(getDelay()); 356 if (d == 0) { 357 // If the delay is zero or null, then just start immediately 358 executeTaskNow(task); 359 } else { 360 schedule(createTimerTask(task), d); 361 } 362 } else { 363 // We are executing as a result of an iteration, not a fresh start. 364 // If the runPeriod (time between the last run and now) exceeds the cumulativePeriod, then 365 // we need to execute immediately. Otherwise, we will pause until the cumulativePeriod has 366 // been reached, and then run. 367 double cumulative = normalize(getCumulativePeriod()); // Can never be null. 368 double runPeriod = clock() - lastRunTime; 369 if (runPeriod < cumulative) { 370 // Pause and then execute 371 assert delayTask == null; 372 schedule(createTimerTask(task), (long) (cumulative - runPeriod)); 373 } else { 374 // Execute immediately 375 executeTaskNow(task); 376 } 377 } 378 } 379 380 /** 381 * @inheritDoc 382 * 383 * Implementation Note: Subclasses which override this method must call this super implementation. 384 */ 385 @Override protected void succeeded() { 386 super.succeeded(); 387 lastValue.set(getValue()); 388 // Reset the cumulative time 389 Duration d = getPeriod(); 390 setCumulativePeriod(d); 391 // Call the super implementation of reset, which will not cause us 392 // to think this is a new fresh start. 393 ScheduledService.this.superReset(); 394 assert freshStart == false; 395 // Fire it up! 396 ScheduledService.this.start(); 397 } 398 399 /** 400 * @inheritDoc 401 * 402 * Implementation Note: Subclasses which override this method must call this super implementation. 403 */ 404 @Override protected void cancelled() { 405 super.cancelled(); 406 // Stop the delayTask if it exists 407 if (delayTask != null) { 408 delayTask.cancel(); 409 delayTask = null; 410 } 411 } 412 413 /** 414 * @inheritDoc 415 * 416 * Implementation Note: Subclasses which override this method must call this super implementation. 417 */ 418 @Override protected void failed() { 419 super.failed(); 420 assert delayTask == null; 421 // Restart as necessary 422 setCurrentFailureCount(getCurrentFailureCount() + 1); 423 if (getRestartOnFailure() && getMaximumFailureCount() > getCurrentFailureCount()) { 424 // We've not yet maxed out the number of failures we can 425 // encounter, so we're going to iterate 426 Callback<ScheduledService<?>,Duration> func = getBackoffStrategy(); 427 if (func != null) { 428 Duration d = func.call(this); 429 setCumulativePeriod(d); 430 } 431 432 ScheduledService.this.superReset(); 433 assert freshStart == false; 434 ScheduledService.this.start(); 435 } else { 436 // We've maxed out, so do nothing and things will just stop. 437 } 438 } 439 440 /** 441 * @inheritDoc 442 * 443 * Implementation Note: Subclasses which override this method must call this super implementation. 444 */ 445 @Override public void reset() { 446 super.reset(); 447 setCumulativePeriod(getPeriod()); 448 lastValue.set(null); 449 setCurrentFailureCount(0); 450 lastRunTime = 0L; 451 freshStart = true; 452 } 453 454 /** 455 * This method exists only for testing purposes. The normal implementation 456 * will delegate to a java.util.Timer, however during testing we want to simply 457 * inspect the value for the delay and execute immediately. 458 * @param task not null 459 * @param delay >= 0 460 */ 461 void schedule(TimerTask task, long delay) { 462 DELAY_TIMER.schedule(task, delay); 463 } 464 465 /** 466 * This method only exists for the sake of testing. 467 * @return freshStart 468 */ 469 boolean isFreshStart() { return freshStart; } 470 471 /** 472 * Gets the time of the current clock. At runtime this is simply getting the results 473 * of System.currentTimeMillis, however during testing this is hammered so as to return 474 * a time that works well during testing. 475 * @return The clock time 476 */ 477 long clock() { 478 return System.currentTimeMillis(); 479 } 480 481 /** 482 * Called by this class when we need to avoid calling this class' implementation of 483 * reset which has the side effect of resetting the "freshStart", currentFailureCount, 484 * and other state. 485 */ 486 private void superReset() { 487 super.reset(); 488 } 489 490 /** 491 * Creates the TimerTask used for delaying execution. The delay can either be due to 492 * the initial delay (if this is a freshStart), or it can be the computed delay in order 493 * to execute the task on its fixed schedule. 494 * 495 * @param task must not be null. 496 * @return the delay TimerTask. 497 */ 498 private TimerTask createTimerTask(final Task<V> task) { 499 assert task != null; 500 return new TimerTask() { 501 @Override public void run() { 502 Runnable r = new Runnable() { 503 @Override public void run() { 504 executeTaskNow(task); 505 delayTask = null; 506 } 507 }; 508 509 // We must make sure that executeTaskNow is called from the FX thread. 510 // This must happen on th FX thread because the super implementation of 511 // executeTask is going to call getExecutor so it can use any user supplied 512 // executor, and this property can only be read on the FX thread. 513 if (isFxApplicationThread()) { 514 r.run(); 515 } else { 516 runLater(r); 517 } 518 } 519 }; 520 } 521 522 /** 523 * Called when it is time to actually execute the task (any delay has by now been 524 * accounted for). Essentially this ends up simply calling the super implementation 525 * of executeTask and doing some bookkeeping. 526 * 527 * @param task must not be null 528 */ 529 private void executeTaskNow(Task<V> task) { 530 assert task != null; 531 lastRunTime = clock(); 532 freshStart = false; 533 super.executeTask(task); 534 } 535 536 /** 537 * Normalize our handling of Durations according to the class documentation. 538 * @param d can be null 539 * @return a double representing the millis. 540 */ 541 private static double normalize(Duration d) { 542 if (d == null || d.isUnknown()) return 0; 543 if (d.isIndefinite()) return Double.MAX_VALUE; 544 return d.toMillis(); 545 } 546}