001 /*
002 * Copyright 2014-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2014-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.util;
022
023
024
025 import java.io.BufferedReader;
026 import java.io.File;
027 import java.io.FileReader;
028 import java.io.IOException;
029 import java.io.PrintWriter;
030 import java.io.Reader;
031 import java.util.ArrayList;
032 import java.util.Arrays;
033 import java.util.Collections;
034 import java.util.Iterator;
035 import java.util.LinkedHashMap;
036 import java.util.LinkedHashSet;
037 import java.util.LinkedList;
038 import java.util.List;
039 import java.util.Map;
040 import java.util.Set;
041 import java.util.concurrent.CountDownLatch;
042 import java.util.concurrent.TimeUnit;
043 import java.util.regex.Pattern;
044
045 import com.unboundid.util.args.ArgumentException;
046 import com.unboundid.util.args.DurationArgument;
047
048 import static com.unboundid.util.Debug.*;
049 import static com.unboundid.util.UtilityMessages.*;
050
051
052
053 /**
054 * This class allows a FixedRateBarrier to change dynamically. The rate changes
055 * are governed by lines read from a {@code Reader} (typically backed by a
056 * file). The input starts with a header that provides some global options and
057 * then has a list of lines, where each line contains a single rate per second,
058 * a comma, and a duration to maintain that rate. Rates are specified as an
059 * absolute rate per second or as a rate relative to the base rate per second.
060 * The duration is an integer followed by a time unit (ms=milliseconds,
061 * s=seconds, m=minutes, h=hours, and d=days).
062 * <BR><BR>
063 * The following simple example will run at a target rate of 1000 per second
064 * for one minute, and then 10000 per second for 10 seconds.
065 * <pre>
066 * # format=rate-duration
067 * 1000,1m
068 * 10000,10s
069 * </pre>
070 * <BR>
071 * The following example has a default duration of one minute, and will repeat
072 * the two intervals until this RateAdjustor is shut down. The first interval
073 * is run for the default of 1 minute at two and half times the base rate, and
074 * then run for 10 seconds at 10000 per second.
075 * <pre>
076 * # format=rate-duration
077 * # default-duration=1m
078 * # repeat=true
079 * 2.5X
080 * 10000,10s
081 * </pre>
082 * A {@code RateAdjustor} is a daemon thread. It is necessary to call the
083 * {@code start()} method to start the thread and begin the rate changes.
084 * Once this finished processing the rates, the thread will complete.
085 * It can be stopped prematurely by calling {@code shutDown()}.
086 * <BR><BR>
087 * The header can contain the following options:
088 * <UL>
089 * <LI>{@code format} (required): This must currently have the value
090 * {@code rate-duration}.</LI>
091 * <LI>{@code default-duration} (optional): This can specify a default
092 * duration for intervals that do not include a duration. The format
093 * is an integer followed by a time unit as described above.</LI>
094 * <LI>{@code repeat} (optional): If this has a value of {@code true}, then
095 * the rates in the input will be repeated until {@code shutDown()} is
096 * called.</LI>
097 * </UL>
098 */
099 @ThreadSafety(level = ThreadSafetyLevel.MOSTLY_THREADSAFE)
100 public final class RateAdjustor extends Thread
101 {
102 /**
103 * This starts a comment in the input.
104 */
105 public static final char COMMENT_START = '#';
106
107
108
109 /**
110 * The text that must appear on a line by itself in order to denote that the
111 * end of the file header has been reached.
112 */
113 public static final String END_HEADER_TEXT = "END HEADER";
114
115
116
117 /**
118 * The header key that represents the default duration.
119 */
120 public static final String DEFAULT_DURATION_KEY = "default-duration";
121
122
123
124 /**
125 * The header key that represents the format of the file.
126 */
127 public static final String FORMAT_KEY = "format";
128
129
130
131 /**
132 * The value of the format key that represents a list of rates and durations
133 * within the input file.
134 */
135 public static final String FORMAT_VALUE_RATE_DURATION = "rate-and-duration";
136
137
138
139 /**
140 * A list of all formats that we support.
141 */
142 public static final List<String> FORMATS =
143 Arrays.asList(FORMAT_VALUE_RATE_DURATION);
144
145
146
147 /**
148 * The header key that represents whether the input should be repeated.
149 */
150 public static final String REPEAT_KEY = "repeat";
151
152
153
154 /**
155 * A list of all header keys that we support.
156 */
157 public static final List<String> KEYS =
158 Arrays.asList(DEFAULT_DURATION_KEY, FORMAT_KEY, REPEAT_KEY);
159
160
161
162 // Other headers to consider:
163 // * rate-multiplier, so you can easily proportionally increase or decrease
164 // every target rate without changing all the target rates directly.
165 // * duration-multiplier, so you can easily proportionally increase or
166 // decrease the length of time to spend at target rates.
167 // * rate-change-behavior, so you can specify the behavior that should be
168 // exhibited when transitioning from one rate to another (e.g., instant
169 // jump, linear acceleration, sine-based acceleration, etc.).
170 // * jitter, so we can introduce some amount of random jitter in the target
171 // rate (in which the actual target rate may be frequently adjusted to be
172 // slightly higher or lower than the designated target rate).
173 // * spike, so we can introduce periodic, substantial increases in the target
174 // rate.
175
176
177
178 // The barrier whose rate is adjusted.
179 private final FixedRateBarrier barrier;
180
181 // A list of rates per second and the number of milliseconds that the
182 // specified rate should be maintained.
183 private final List<ObjectPair<Double,Long>> ratesAndDurations;
184
185 // If this is true, then the ratesAndDurations will be repeated until this is
186 // shut down.
187 private final boolean repeat;
188
189 // Set to true when this should shut down.
190 private volatile boolean shutDown = false;
191
192 // This is used to make sure we set the initial rate before start() returns.
193 private final CountDownLatch initialRateSetLatch = new CountDownLatch(1);
194
195 // This allows us to interrupt when we are sleeping.
196 private final WakeableSleeper sleeper = new WakeableSleeper();
197
198
199
200 /**
201 * Returns a new RateAdjustor with the specified parameters. See the
202 * class-level javadoc for more information.
203 *
204 * @param barrier The barrier to update based on the specified
205 * rates.
206 * @param baseRatePerSecond The baseline rate per second, or {@code null}
207 * if none was specified.
208 * @param rates A file containing a list of rates and durations
209 * as described in the class-level javadoc.
210 *
211 * @return A new RateAdjustor constructed from the specified parameters.
212 *
213 * @throws IOException If there is a problem reading from
214 * the rates Reader.
215 * @throws IllegalArgumentException If there is a problem with the rates
216 * input.
217 */
218 public static RateAdjustor newInstance(final FixedRateBarrier barrier,
219 final Integer baseRatePerSecond,
220 final File rates)
221 throws IOException, IllegalArgumentException
222 {
223 final Reader reader = new FileReader(rates);
224 return new RateAdjustor(
225 barrier,
226 (baseRatePerSecond == null) ? 0 : baseRatePerSecond,
227 reader);
228 }
229
230
231
232 /**
233 * Retrieves a string that may be used as the description of the argument that
234 * specifies the path to a variable rate data file for use in conjunction with
235 * this rate adjustor.
236 *
237 * @param genArgName The name of the argument that may be used to generate a
238 * sample variable rate data file.
239 *
240 * @return A string that may be used as the description of the argument that
241 * specifies the path to a variable rate data file for use in
242 * conjunction with this rate adjustor.
243 */
244 public static String getVariableRateDataArgumentDescription(
245 final String genArgName)
246 {
247 return INFO_RATE_ADJUSTOR_VARIABLE_RATE_DATA_ARG_DESCRIPTION.get(
248 genArgName);
249 }
250
251
252
253 /**
254 * Retrieves a string that may be used as the description of the argument that
255 * generates a sample variable rate data file that serves as documentation of
256 * the variable rate data format.
257 *
258 * @param dataFileArgName The name of the argument that specifies the path
259 * to a file
260 *
261 * @return A string that may be used as the description of the argument that
262 * generates a sample variable rate data file that serves as
263 * documentation of the variable rate data format.
264 */
265 public static String getGenerateSampleVariableRateFileDescription(
266 final String dataFileArgName)
267 {
268 return INFO_RATE_ADJUSTOR_GENERATE_SAMPLE_RATE_FILE_ARG_DESCRIPTION.get(
269 dataFileArgName);
270 }
271
272
273
274 /**
275 * Writes a sample variable write data file to the specified location.
276 *
277 * @param f The path to the file to be written.
278 *
279 * @throws IOException If a problem is encountered while writing to the
280 * specified file.
281 */
282 public static void writeSampleVariableRateFile(final File f)
283 throws IOException
284 {
285 final PrintWriter w = new PrintWriter(f);
286 try
287 {
288 w.println("# This is an example variable rate data file. All blank " +
289 "lines will be ignored.");
290 w.println("# All lines starting with the '#' character are considered " +
291 "comments and will");
292 w.println("# also be ignored.");
293 w.println();
294 w.println("# The beginning of the file must be a header containing " +
295 "properties pertaining");
296 w.println("# to the variable rate data. All headers must be in the " +
297 "format 'name=value',");
298 w.println("# in which any spaces surrounding the equal sign will be " +
299 "ignored.");
300 w.println();
301 w.println("# The first header should be the 'format' header, which " +
302 "specifies the format");
303 w.println("# for the variable rate data file. This header is " +
304 "required. At present, the");
305 w.println("# only supported format is 'rate-and-duration', although " +
306 "additional formats may");
307 w.println("# be added in the future.");
308 w.println("format = rate-and-duration");
309 w.println();
310 w.println("# The optional 'default-duration' header may be used to " +
311 "specify a duration that");
312 w.println("# will be used for any interval that does not explicitly " +
313 "specify a duration.");
314 w.println("# The duration must consist of a positive integer value " +
315 "followed by a time");
316 w.println("# unit (with zero or more spaces separating the integer " +
317 "value from the unit).");
318 w.println("# The supported time units are:");
319 w.println("#");
320 w.println("# - nanoseconds, nanosecond, nanos, nano, ns");
321 w.println("# - microseconds, microseconds, micros, micro, us");
322 w.println("# - milliseconds, millisecond, millis, milli, ms");
323 w.println("# - seconds, second, secs, sec, s");
324 w.println("# - minutes, minute, mins, min, m");
325 w.println("# - hours, hour, hrs, hr, h");
326 w.println("# - days, day, d");
327 w.println("#");
328 w.println("# If no 'default-duration' header is present, then every " +
329 "data interval must");
330 w.println("# include an explicitly-specified duration.");
331 w.println("default-duration = 10 seconds");
332 w.println();
333 w.println("# The optional 'repeat' header may be used to indicate how " +
334 "the tool should");
335 w.println("# behave once the end of the variable rate data definitions " +
336 "has been reached.");
337 w.println("# If the 'repeat' header is present with a value of 'true', " +
338 "then the tool will");
339 w.println("# operate in an endless loop, returning to the beginning of " +
340 "the variable rate");
341 w.println("# definitions once the end has been reached. If the " +
342 "'repeat' header is present");
343 w.println("# with a value of 'false', or if the 'repeat' header is " +
344 "absent, then the tool");
345 w.println("# will exit after it has processed all of the variable " +
346 "rate definitions.");
347 w.println("repeat = true");
348 w.println();
349 w.println("# After all header properties have been specified, the end " +
350 "of the header must");
351 w.println("# be signified with a line containing only the text 'END " +
352 "HEADER'.");
353 w.println("END HEADER");
354 w.println();
355 w.println();
356 w.println("# After the header is complete, the variable rate " +
357 "definitions should be");
358 w.println("# provided. Each definition should be given on a line by " +
359 "itself, and should");
360 w.println("# contain a target rate per second and an optional length " +
361 "of time to maintain");
362 w.println("# that rate.");
363 w.println("#");
364 w.println("# The target rate must always be present in a variable " +
365 "rate definition. It may");
366 w.println("# be either a positive integer value that specifies the " +
367 "absolute target rate");
368 w.println("# per second (e.g., a value of '1000' indicates a target " +
369 "rate of 1000");
370 w.println("# operations per second), or it may be a floating-point " +
371 "value followed by the");
372 w.println("# letter 'x' to indicate that it is a multiplier of the " +
373 "value specified by the");
374 w.println("# '--ratePerSecond' argument (e.g., if the " +
375 "'--ratePerSecond' argument is");
376 w.println("# present with a value of 1000, then a target rate value " +
377 "of '0.75x' indicates a");
378 w.println("# target rate that is 75% of the '--ratePerSecond' value, " +
379 "or 750 operations per");
380 w.println("# second). If the latter format is used, then the " +
381 "'--ratePerSecond' argument");
382 w.println("# must be provided.");
383 w.println("#");
384 w.println("# The duration may optionally be present in a variable " +
385 "rate definition. If");
386 w.println("# present, it must be separated from the target rate by a " +
387 "comma (and there may");
388 w.println("# be zero or more spaces on either side of the comma). " +
389 "The duration must be in");
390 w.println("# the same format as specified in the description of the " +
391 "'default-duration'");
392 w.println("# header above (i.e., a positive integer followed by a " +
393 "time unit). If a");
394 w.println("# variable rate definition does not include a duration, " +
395 "then the");
396 w.println("# 'default-duration' header must have been specified, and " +
397 "that default duration");
398 w.println("# will be used for that variable rate definition.");
399 w.println("#");
400 w.println("# The following variable rate definitions may be used to " +
401 "stairstep the target");
402 w.println("# rate from 1000 operations per second to 10000 operations " +
403 "per second, in");
404 w.println("# increments of 1000 operations per second, spending one " +
405 "minute at each level.");
406 w.println("# If the 'repeat' header is present with a value of 'true', " +
407 "then the process");
408 w.println("# will start back over at 1000 operations per second after " +
409 "completing one");
410 w.println("# minute at 10000 operations per second. Otherwise, the " +
411 "tool will exit after");
412 w.println("# completing the 10000 operation-per-second interval.");
413 w.println("1000, 1 minute");
414 w.println("2000, 1 minute");
415 w.println("3000, 1 minute");
416 w.println("4000, 1 minute");
417 w.println("5000, 1 minute");
418 w.println("6000, 1 minute");
419 w.println("7000, 1 minute");
420 w.println("8000, 1 minute");
421 w.println("9000, 1 minute");
422 w.println("10000, 1 minute");
423 w.println();
424 w.println();
425 w.println("# Additional sample rate definitions that represent common " +
426 "load patterns are");
427 w.println("# provided below. Each of these patterns makes use of the " +
428 "relative format for");
429 w.println("# the target rate and therefore require the " +
430 "'--ratePerSecond' argument to");
431 w.println("# specify the target rate. These sample rate definitions " +
432 "are commented out to");
433 w.println("# prevent them from being interpreted by default.");
434 w.println();
435 w.println();
436 w.println("# Example: Square Rate");
437 w.println("#");
438 w.println("# This pattern starts with a rate of zero operations per " +
439 "second, then");
440 w.println("# immediately jumps to a rate of 100% of the target rate. " +
441 "A graph of the load");
442 w.println("# generated by repeating iterations of this pattern " +
443 "represents a series of");
444 w.println("# squares that are alternately missing the top and bottom " +
445 "edges.");
446 w.println("#");
447 w.println("#0.00x");
448 w.println("#1.00x");
449 w.println();
450 w.println();
451 w.println("# Example: Stairstep Rate");
452 w.println("#");
453 w.println("# This pattern starts with a rate that is 10% of the target " +
454 "rate, then jumps to");
455 w.println("# 20% of the target rate, then 30%, 40%, 50%, etc. until it " +
456 "reaches 100% of the");
457 w.println("# target rate. A graph of the load generated by a single " +
458 "iteration of this");
459 w.println("# pattern represents a series of stair steps.");
460 w.println("#");
461 w.println("#0.1x");
462 w.println("#0.2x");
463 w.println("#0.3x");
464 w.println("#0.4x");
465 w.println("#0.5x");
466 w.println("#0.6x");
467 w.println("#0.7x");
468 w.println("#0.8x");
469 w.println("#0.9x");
470 w.println("#1.0x");
471 w.println();
472 w.println();
473 w.println("# Example: Sine Rate");
474 w.println("#");
475 w.println("# This pattern starts with a rate of zero operations per " +
476 "second and increases");
477 w.println("# to # 100% of the target rate in a pattern that is gradual " +
478 "at first, rapid in");
479 w.println("# the middle, and then gradual again at the end, and then " +
480 "decreases back to");
481 w.println("# zero in a mirror image of the ascent. A graph of the " +
482 "load generated by this");
483 w.println("# pattern resembles a sine wave, but starting at the " +
484 "lowest point in the trough");
485 w.println("# of the wave (mathematically, represented by the function " +
486 "'y=sin(x-pi/2)+1').");
487 w.println("#");
488 w.println("#0.000x");
489 w.println("#0.001x");
490 w.println("#0.002x");
491 w.println("#0.004x");
492 w.println("#0.006x");
493 w.println("#0.009x");
494 w.println("#0.012x");
495 w.println("#0.016x");
496 w.println("#0.020x");
497 w.println("#0.024x");
498 w.println("#0.030x");
499 w.println("#0.035x");
500 w.println("#0.041x");
501 w.println("#0.048x");
502 w.println("#0.054x");
503 w.println("#0.062x");
504 w.println("#0.070x");
505 w.println("#0.078x");
506 w.println("#0.086x");
507 w.println("#0.095x");
508 w.println("#0.105x");
509 w.println("#0.115x");
510 w.println("#0.125x");
511 w.println("#0.136x");
512 w.println("#0.146x");
513 w.println("#0.158x");
514 w.println("#0.169x");
515 w.println("#0.181x");
516 w.println("#0.194x");
517 w.println("#0.206x");
518 w.println("#0.219x");
519 w.println("#0.232x");
520 w.println("#0.245x");
521 w.println("#0.259x");
522 w.println("#0.273x");
523 w.println("#0.287x");
524 w.println("#0.301x");
525 w.println("#0.316x");
526 w.println("#0.331x");
527 w.println("#0.345x");
528 w.println("#0.361x");
529 w.println("#0.376x");
530 w.println("#0.391x");
531 w.println("#0.406x");
532 w.println("#0.422x");
533 w.println("#0.437x");
534 w.println("#0.453x");
535 w.println("#0.469x");
536 w.println("#0.484x");
537 w.println("#0.500x");
538 w.println("#0.500x");
539 w.println("#0.516x");
540 w.println("#0.531x");
541 w.println("#0.547x");
542 w.println("#0.563x");
543 w.println("#0.578x");
544 w.println("#0.594x");
545 w.println("#0.609x");
546 w.println("#0.624x");
547 w.println("#0.639x");
548 w.println("#0.655x");
549 w.println("#0.669x");
550 w.println("#0.684x");
551 w.println("#0.699x");
552 w.println("#0.713x");
553 w.println("#0.727x");
554 w.println("#0.741x");
555 w.println("#0.755x");
556 w.println("#0.768x");
557 w.println("#0.781x");
558 w.println("#0.794x");
559 w.println("#0.806x");
560 w.println("#0.819x");
561 w.println("#0.831x");
562 w.println("#0.842x");
563 w.println("#0.854x");
564 w.println("#0.864x");
565 w.println("#0.875x");
566 w.println("#0.885x");
567 w.println("#0.895x");
568 w.println("#0.905x");
569 w.println("#0.914x");
570 w.println("#0.922x");
571 w.println("#0.930x");
572 w.println("#0.938x");
573 w.println("#0.946x");
574 w.println("#0.952x");
575 w.println("#0.959x");
576 w.println("#0.965x");
577 w.println("#0.970x");
578 w.println("#0.976x");
579 w.println("#0.980x");
580 w.println("#0.984x");
581 w.println("#0.988x");
582 w.println("#0.991x");
583 w.println("#0.994x");
584 w.println("#0.996x");
585 w.println("#0.998x");
586 w.println("#0.999x");
587 w.println("#1.000x");
588 w.println("#1.000x");
589 w.println("#1.000x");
590 w.println("#0.999x");
591 w.println("#0.998x");
592 w.println("#0.996x");
593 w.println("#0.994x");
594 w.println("#0.991x");
595 w.println("#0.988x");
596 w.println("#0.984x");
597 w.println("#0.980x");
598 w.println("#0.976x");
599 w.println("#0.970x");
600 w.println("#0.965x");
601 w.println("#0.959x");
602 w.println("#0.952x");
603 w.println("#0.946x");
604 w.println("#0.938x");
605 w.println("#0.930x");
606 w.println("#0.922x");
607 w.println("#0.914x");
608 w.println("#0.905x");
609 w.println("#0.895x");
610 w.println("#0.885x");
611 w.println("#0.875x");
612 w.println("#0.864x");
613 w.println("#0.854x");
614 w.println("#0.842x");
615 w.println("#0.831x");
616 w.println("#0.819x");
617 w.println("#0.806x");
618 w.println("#0.794x");
619 w.println("#0.781x");
620 w.println("#0.768x");
621 w.println("#0.755x");
622 w.println("#0.741x");
623 w.println("#0.727x");
624 w.println("#0.713x");
625 w.println("#0.699x");
626 w.println("#0.684x");
627 w.println("#0.669x");
628 w.println("#0.655x");
629 w.println("#0.639x");
630 w.println("#0.624x");
631 w.println("#0.609x");
632 w.println("#0.594x");
633 w.println("#0.578x");
634 w.println("#0.563x");
635 w.println("#0.547x");
636 w.println("#0.531x");
637 w.println("#0.516x");
638 w.println("#0.500x");
639 w.println("#0.484x");
640 w.println("#0.469x");
641 w.println("#0.453x");
642 w.println("#0.437x");
643 w.println("#0.422x");
644 w.println("#0.406x");
645 w.println("#0.391x");
646 w.println("#0.376x");
647 w.println("#0.361x");
648 w.println("#0.345x");
649 w.println("#0.331x");
650 w.println("#0.316x");
651 w.println("#0.301x");
652 w.println("#0.287x");
653 w.println("#0.273x");
654 w.println("#0.259x");
655 w.println("#0.245x");
656 w.println("#0.232x");
657 w.println("#0.219x");
658 w.println("#0.206x");
659 w.println("#0.194x");
660 w.println("#0.181x");
661 w.println("#0.169x");
662 w.println("#0.158x");
663 w.println("#0.146x");
664 w.println("#0.136x");
665 w.println("#0.125x");
666 w.println("#0.115x");
667 w.println("#0.105x");
668 w.println("#0.095x");
669 w.println("#0.086x");
670 w.println("#0.078x");
671 w.println("#0.070x");
672 w.println("#0.062x");
673 w.println("#0.054x");
674 w.println("#0.048x");
675 w.println("#0.041x");
676 w.println("#0.035x");
677 w.println("#0.030x");
678 w.println("#0.024x");
679 w.println("#0.020x");
680 w.println("#0.016x");
681 w.println("#0.012x");
682 w.println("#0.009x");
683 w.println("#0.006x");
684 w.println("#0.004x");
685 w.println("#0.002x");
686 w.println("#0.001x");
687 w.println("#0.000x");
688 w.println();
689 w.println();
690 w.println("# Example: Sawtooth Rate");
691 w.println("#");
692 w.println("# This pattern starts with a rate of zero operations per " +
693 "second and increases");
694 w.println("# linearly to 100% of the target rate. A graph of the load " +
695 "generated by a");
696 w.println("# single iteration of this pattern resembles the hypotenuse " +
697 "of a right");
698 w.println("# triangle, and a graph of multiple iterations resembles " +
699 "the teeth of a saw");
700 w.println("# blade.");
701 w.println("#");
702 w.println("#0.00x");
703 w.println("#0.01x");
704 w.println("#0.02x");
705 w.println("#0.03x");
706 w.println("#0.04x");
707 w.println("#0.05x");
708 w.println("#0.06x");
709 w.println("#0.07x");
710 w.println("#0.08x");
711 w.println("#0.09x");
712 w.println("#0.10x");
713 w.println("#0.11x");
714 w.println("#0.12x");
715 w.println("#0.13x");
716 w.println("#0.14x");
717 w.println("#0.15x");
718 w.println("#0.16x");
719 w.println("#0.17x");
720 w.println("#0.18x");
721 w.println("#0.19x");
722 w.println("#0.20x");
723 w.println("#0.21x");
724 w.println("#0.22x");
725 w.println("#0.23x");
726 w.println("#0.24x");
727 w.println("#0.25x");
728 w.println("#0.26x");
729 w.println("#0.27x");
730 w.println("#0.28x");
731 w.println("#0.29x");
732 w.println("#0.30x");
733 w.println("#0.31x");
734 w.println("#0.32x");
735 w.println("#0.33x");
736 w.println("#0.34x");
737 w.println("#0.35x");
738 w.println("#0.36x");
739 w.println("#0.37x");
740 w.println("#0.38x");
741 w.println("#0.39x");
742 w.println("#0.40x");
743 w.println("#0.41x");
744 w.println("#0.42x");
745 w.println("#0.43x");
746 w.println("#0.44x");
747 w.println("#0.45x");
748 w.println("#0.46x");
749 w.println("#0.47x");
750 w.println("#0.48x");
751 w.println("#0.49x");
752 w.println("#0.50x");
753 w.println("#0.51x");
754 w.println("#0.52x");
755 w.println("#0.53x");
756 w.println("#0.54x");
757 w.println("#0.55x");
758 w.println("#0.56x");
759 w.println("#0.57x");
760 w.println("#0.58x");
761 w.println("#0.59x");
762 w.println("#0.60x");
763 w.println("#0.61x");
764 w.println("#0.62x");
765 w.println("#0.63x");
766 w.println("#0.64x");
767 w.println("#0.65x");
768 w.println("#0.66x");
769 w.println("#0.67x");
770 w.println("#0.68x");
771 w.println("#0.69x");
772 w.println("#0.70x");
773 w.println("#0.71x");
774 w.println("#0.72x");
775 w.println("#0.73x");
776 w.println("#0.74x");
777 w.println("#0.75x");
778 w.println("#0.76x");
779 w.println("#0.77x");
780 w.println("#0.78x");
781 w.println("#0.79x");
782 w.println("#0.80x");
783 w.println("#0.81x");
784 w.println("#0.82x");
785 w.println("#0.83x");
786 w.println("#0.84x");
787 w.println("#0.85x");
788 w.println("#0.86x");
789 w.println("#0.87x");
790 w.println("#0.88x");
791 w.println("#0.89x");
792 w.println("#0.90x");
793 w.println("#0.91x");
794 w.println("#0.92x");
795 w.println("#0.93x");
796 w.println("#0.94x");
797 w.println("#0.95x");
798 w.println("#0.96x");
799 w.println("#0.97x");
800 w.println("#0.98x");
801 w.println("#0.99x");
802 w.println("#1.00x");
803 w.println();
804 w.println();
805 w.println("# Example: Triangle Rate");
806 w.println("#");
807 w.println("# This pattern starts with a rate of zero operations per " +
808 "second and increases");
809 w.println("# linearly to 100% of the target rate before decreasing " +
810 "linearly back to 0%.");
811 w.println("# A graph of the load generated by a single iteration of " +
812 "this tool is like that");
813 w.println("# of the sawtooth pattern above followed immediately by its " +
814 "mirror image.");
815 w.println("#");
816 w.println("#0.00x");
817 w.println("#0.01x");
818 w.println("#0.02x");
819 w.println("#0.03x");
820 w.println("#0.04x");
821 w.println("#0.05x");
822 w.println("#0.06x");
823 w.println("#0.07x");
824 w.println("#0.08x");
825 w.println("#0.09x");
826 w.println("#0.10x");
827 w.println("#0.11x");
828 w.println("#0.12x");
829 w.println("#0.13x");
830 w.println("#0.14x");
831 w.println("#0.15x");
832 w.println("#0.16x");
833 w.println("#0.17x");
834 w.println("#0.18x");
835 w.println("#0.19x");
836 w.println("#0.20x");
837 w.println("#0.21x");
838 w.println("#0.22x");
839 w.println("#0.23x");
840 w.println("#0.24x");
841 w.println("#0.25x");
842 w.println("#0.26x");
843 w.println("#0.27x");
844 w.println("#0.28x");
845 w.println("#0.29x");
846 w.println("#0.30x");
847 w.println("#0.31x");
848 w.println("#0.32x");
849 w.println("#0.33x");
850 w.println("#0.34x");
851 w.println("#0.35x");
852 w.println("#0.36x");
853 w.println("#0.37x");
854 w.println("#0.38x");
855 w.println("#0.39x");
856 w.println("#0.40x");
857 w.println("#0.41x");
858 w.println("#0.42x");
859 w.println("#0.43x");
860 w.println("#0.44x");
861 w.println("#0.45x");
862 w.println("#0.46x");
863 w.println("#0.47x");
864 w.println("#0.48x");
865 w.println("#0.49x");
866 w.println("#0.50x");
867 w.println("#0.51x");
868 w.println("#0.52x");
869 w.println("#0.53x");
870 w.println("#0.54x");
871 w.println("#0.55x");
872 w.println("#0.56x");
873 w.println("#0.57x");
874 w.println("#0.58x");
875 w.println("#0.59x");
876 w.println("#0.60x");
877 w.println("#0.61x");
878 w.println("#0.62x");
879 w.println("#0.63x");
880 w.println("#0.64x");
881 w.println("#0.65x");
882 w.println("#0.66x");
883 w.println("#0.67x");
884 w.println("#0.68x");
885 w.println("#0.69x");
886 w.println("#0.70x");
887 w.println("#0.71x");
888 w.println("#0.72x");
889 w.println("#0.73x");
890 w.println("#0.74x");
891 w.println("#0.75x");
892 w.println("#0.76x");
893 w.println("#0.77x");
894 w.println("#0.78x");
895 w.println("#0.79x");
896 w.println("#0.80x");
897 w.println("#0.81x");
898 w.println("#0.82x");
899 w.println("#0.83x");
900 w.println("#0.84x");
901 w.println("#0.85x");
902 w.println("#0.86x");
903 w.println("#0.87x");
904 w.println("#0.88x");
905 w.println("#0.89x");
906 w.println("#0.90x");
907 w.println("#0.91x");
908 w.println("#0.92x");
909 w.println("#0.93x");
910 w.println("#0.94x");
911 w.println("#0.95x");
912 w.println("#0.96x");
913 w.println("#0.97x");
914 w.println("#0.98x");
915 w.println("#0.99x");
916 w.println("#1.00x");
917 w.println("#0.99x");
918 w.println("#0.98x");
919 w.println("#0.97x");
920 w.println("#0.96x");
921 w.println("#0.95x");
922 w.println("#0.94x");
923 w.println("#0.93x");
924 w.println("#0.92x");
925 w.println("#0.91x");
926 w.println("#0.90x");
927 w.println("#0.89x");
928 w.println("#0.88x");
929 w.println("#0.87x");
930 w.println("#0.86x");
931 w.println("#0.85x");
932 w.println("#0.84x");
933 w.println("#0.83x");
934 w.println("#0.82x");
935 w.println("#0.81x");
936 w.println("#0.80x");
937 w.println("#0.79x");
938 w.println("#0.78x");
939 w.println("#0.77x");
940 w.println("#0.76x");
941 w.println("#0.75x");
942 w.println("#0.74x");
943 w.println("#0.73x");
944 w.println("#0.72x");
945 w.println("#0.71x");
946 w.println("#0.70x");
947 w.println("#0.69x");
948 w.println("#0.68x");
949 w.println("#0.67x");
950 w.println("#0.66x");
951 w.println("#0.65x");
952 w.println("#0.64x");
953 w.println("#0.63x");
954 w.println("#0.62x");
955 w.println("#0.61x");
956 w.println("#0.60x");
957 w.println("#0.59x");
958 w.println("#0.58x");
959 w.println("#0.57x");
960 w.println("#0.56x");
961 w.println("#0.55x");
962 w.println("#0.54x");
963 w.println("#0.53x");
964 w.println("#0.52x");
965 w.println("#0.51x");
966 w.println("#0.50x");
967 w.println("#0.49x");
968 w.println("#0.48x");
969 w.println("#0.47x");
970 w.println("#0.46x");
971 w.println("#0.45x");
972 w.println("#0.44x");
973 w.println("#0.43x");
974 w.println("#0.42x");
975 w.println("#0.41x");
976 w.println("#0.40x");
977 w.println("#0.39x");
978 w.println("#0.38x");
979 w.println("#0.37x");
980 w.println("#0.36x");
981 w.println("#0.35x");
982 w.println("#0.34x");
983 w.println("#0.33x");
984 w.println("#0.32x");
985 w.println("#0.31x");
986 w.println("#0.30x");
987 w.println("#0.29x");
988 w.println("#0.28x");
989 w.println("#0.27x");
990 w.println("#0.26x");
991 w.println("#0.25x");
992 w.println("#0.24x");
993 w.println("#0.23x");
994 w.println("#0.22x");
995 w.println("#0.21x");
996 w.println("#0.20x");
997 w.println("#0.19x");
998 w.println("#0.18x");
999 w.println("#0.17x");
1000 w.println("#0.16x");
1001 w.println("#0.15x");
1002 w.println("#0.14x");
1003 w.println("#0.13x");
1004 w.println("#0.12x");
1005 w.println("#0.11x");
1006 w.println("#0.10x");
1007 w.println("#0.09x");
1008 w.println("#0.08x");
1009 w.println("#0.07x");
1010 w.println("#0.06x");
1011 w.println("#0.05x");
1012 w.println("#0.04x");
1013 w.println("#0.03x");
1014 w.println("#0.02x");
1015 w.println("#0.01x");
1016 w.println("#0.00x");
1017 w.println();
1018 w.println();
1019 w.println("# Example: 'Hockey Stick' Rate");
1020 w.println("#");
1021 w.println("# This pattern starts with a rate of zero operations per " +
1022 "second and increases");
1023 w.println("# slowly at first before ramping up much more quickly. A " +
1024 "graph of the load");
1025 w.println("# generated by a single iteration of this pattern vaguely " +
1026 "resembles a hockey");
1027 w.println("# stick.");
1028 w.println("#");
1029 w.println("#0.000x");
1030 w.println("#0.000x");
1031 w.println("#0.000x");
1032 w.println("#0.000x");
1033 w.println("#0.000x");
1034 w.println("#0.000x");
1035 w.println("#0.000x");
1036 w.println("#0.000x");
1037 w.println("#0.001x");
1038 w.println("#0.001x");
1039 w.println("#0.001x");
1040 w.println("#0.001x");
1041 w.println("#0.002x");
1042 w.println("#0.002x");
1043 w.println("#0.003x");
1044 w.println("#0.003x");
1045 w.println("#0.004x");
1046 w.println("#0.005x");
1047 w.println("#0.006x");
1048 w.println("#0.007x");
1049 w.println("#0.008x");
1050 w.println("#0.009x");
1051 w.println("#0.011x");
1052 w.println("#0.012x");
1053 w.println("#0.014x");
1054 w.println("#0.016x");
1055 w.println("#0.018x");
1056 w.println("#0.020x");
1057 w.println("#0.022x");
1058 w.println("#0.024x");
1059 w.println("#0.027x");
1060 w.println("#0.030x");
1061 w.println("#0.033x");
1062 w.println("#0.036x");
1063 w.println("#0.039x");
1064 w.println("#0.043x");
1065 w.println("#0.047x");
1066 w.println("#0.051x");
1067 w.println("#0.055x");
1068 w.println("#0.059x");
1069 w.println("#0.064x");
1070 w.println("#0.069x");
1071 w.println("#0.074x");
1072 w.println("#0.080x");
1073 w.println("#0.085x");
1074 w.println("#0.091x");
1075 w.println("#0.097x");
1076 w.println("#0.104x");
1077 w.println("#0.111x");
1078 w.println("#0.118x");
1079 w.println("#0.125x");
1080 w.println("#0.133x");
1081 w.println("#0.141x");
1082 w.println("#0.149x");
1083 w.println("#0.157x");
1084 w.println("#0.166x");
1085 w.println("#0.176x");
1086 w.println("#0.185x");
1087 w.println("#0.195x");
1088 w.println("#0.205x");
1089 w.println("#0.216x");
1090 w.println("#0.227x");
1091 w.println("#0.238x");
1092 w.println("#0.250x");
1093 w.println("#0.262x");
1094 w.println("#0.275x");
1095 w.println("#0.287x");
1096 w.println("#0.301x");
1097 w.println("#0.314x");
1098 w.println("#0.329x");
1099 w.println("#0.343x");
1100 w.println("#0.358x");
1101 w.println("#0.373x");
1102 w.println("#0.389x");
1103 w.println("#0.405x");
1104 w.println("#0.422x");
1105 w.println("#0.439x");
1106 w.println("#0.457x");
1107 w.println("#0.475x");
1108 w.println("#0.493x");
1109 w.println("#0.512x");
1110 w.println("#0.531x");
1111 w.println("#0.551x");
1112 w.println("#0.572x");
1113 w.println("#0.593x");
1114 w.println("#0.614x");
1115 w.println("#0.636x");
1116 w.println("#0.659x");
1117 w.println("#0.681x");
1118 w.println("#0.705x");
1119 w.println("#0.729x");
1120 w.println("#0.754x");
1121 w.println("#0.779x");
1122 w.println("#0.804x");
1123 w.println("#0.831x");
1124 w.println("#0.857x");
1125 w.println("#0.885x");
1126 w.println("#0.913x");
1127 w.println("#0.941x");
1128 w.println("#0.970x");
1129 w.println("#1.000x");
1130 w.println();
1131 }
1132 finally
1133 {
1134 w.close();
1135 }
1136 }
1137
1138
1139
1140 /**
1141 * Constructs a new RateAdjustor with the specified parameters. See the
1142 * class-level javadoc for more information.
1143 *
1144 * @param barrier The barrier to update based on the specified
1145 * rates.
1146 * @param baseRatePerSecond The baseline rate per second, or 0 if none was
1147 * specified.
1148 * @param rates A list of rates and durations as described in
1149 * the class-level javadoc. The reader will
1150 * always be closed before this method returns.
1151 *
1152 * @throws IOException If there is a problem reading from
1153 * the rates Reader.
1154 * @throws IllegalArgumentException If there is a problem with the rates
1155 * input.
1156 */
1157 public RateAdjustor(final FixedRateBarrier barrier,
1158 final long baseRatePerSecond,
1159 final Reader rates)
1160 throws IOException, IllegalArgumentException
1161 {
1162 // Read the header first.
1163 final List<String> lines;
1164 try
1165 {
1166 Validator.ensureNotNull(barrier, rates);
1167 setDaemon(true);
1168 this.barrier = barrier;
1169
1170 lines = readLines(rates);
1171 }
1172 finally
1173 {
1174 rates.close();
1175 }
1176
1177 final Map<String,String> header = consumeHeader(lines);
1178
1179 final Set<String> invalidKeys = new LinkedHashSet<String>(header.keySet());
1180 invalidKeys.removeAll(KEYS);
1181 if (! invalidKeys.isEmpty())
1182 {
1183 throw new IllegalArgumentException(
1184 ERR_RATE_ADJUSTOR_INVALID_KEYS.get(invalidKeys, KEYS));
1185 }
1186
1187 final String format = header.get(FORMAT_KEY);
1188 if (format == null)
1189 {
1190 throw new IllegalArgumentException(ERR_RATE_ADJUSTOR_MISSING_FORMAT.get(
1191 FORMAT_KEY, FORMATS, COMMENT_START));
1192 }
1193
1194 if (! format.equals(FORMAT_VALUE_RATE_DURATION))
1195 {
1196 // For now this is the only format that we support.
1197 throw new IllegalArgumentException(
1198 ERR_RATE_ADJUSTOR_INVALID_FORMAT.get(format, FORMAT_KEY, FORMATS));
1199 }
1200
1201 repeat = Boolean.parseBoolean(header.get(REPEAT_KEY));
1202
1203 // This will be non-zero if it's set in the input.
1204 long defaultDurationMillis = 0;
1205 final String defaultDurationStr = header.get(DEFAULT_DURATION_KEY);
1206 if (defaultDurationStr != null)
1207 {
1208 try
1209 {
1210 defaultDurationMillis = DurationArgument.parseDuration(
1211 defaultDurationStr, TimeUnit.MILLISECONDS);
1212 }
1213 catch (final ArgumentException e)
1214 {
1215 debugException(e);
1216 throw new IllegalArgumentException(
1217 ERR_RATE_ADJUSTOR_INVALID_DEFAULT_DURATION.get(
1218 defaultDurationStr, e.getExceptionMessage()),
1219 e);
1220 }
1221 }
1222
1223 // Now parse out the rates and durations, which will look like this:
1224 // 1000,1s
1225 // 1.5,1d
1226 // 0.5X, 1m
1227 // # Duration can be omitted if default-duration header was included.
1228 // 1000
1229 final List<ObjectPair<Double,Long>> ratesAndDurationList =
1230 new ArrayList<ObjectPair<Double,Long>>(10);
1231 final Pattern splitPattern = Pattern.compile("\\s*,\\s*");
1232 for (final String fullLine: lines)
1233 {
1234 // Strip out comments and white space.
1235 String line = fullLine;
1236 final int commentStart = fullLine.indexOf(COMMENT_START);
1237 if (commentStart >= 0)
1238 {
1239 line = line.substring(0, commentStart);
1240 }
1241 line = line.trim();
1242
1243 if (line.length() == 0)
1244 {
1245 continue;
1246 }
1247
1248 final String[] fields = splitPattern.split(line);
1249 if (!((fields.length == 2) ||
1250 ((fields.length == 1) && defaultDurationMillis != 0)))
1251 {
1252 throw new IllegalArgumentException(ERR_RATE_ADJUSTOR_INVALID_LINE.get(
1253 fullLine, DEFAULT_DURATION_KEY));
1254 }
1255
1256 String rateStr = fields[0];
1257
1258 boolean isRateMultiplier = false;
1259 if (rateStr.endsWith("X") || rateStr.endsWith("x"))
1260 {
1261 rateStr = rateStr.substring(0, rateStr.length() - 1).trim();
1262 isRateMultiplier = true;
1263 }
1264
1265 double rate;
1266 try
1267 {
1268 rate = Double.parseDouble(rateStr);
1269 }
1270 catch (final NumberFormatException e)
1271 {
1272 debugException(e);
1273 throw new IllegalArgumentException(
1274 ERR_RATE_ADJUSTOR_INVALID_RATE.get(rateStr, fullLine), e);
1275 }
1276
1277 // Values that look like 2X are a multiplier on the base rate.
1278 if (isRateMultiplier)
1279 {
1280 if (baseRatePerSecond <= 0)
1281 {
1282 throw new IllegalArgumentException(
1283 ERR_RATE_ADJUSTOR_RELATIVE_RATE_WITHOUT_BASELINE.get(
1284 rateStr, fullLine));
1285 }
1286
1287 rate *= baseRatePerSecond;
1288 }
1289
1290 final long durationMillis;
1291 if (fields.length < 2)
1292 {
1293 durationMillis = defaultDurationMillis;
1294 }
1295 else
1296 {
1297 final String duration = fields[1];
1298 try
1299 {
1300 durationMillis = DurationArgument.parseDuration(
1301 duration, TimeUnit.MILLISECONDS);
1302 }
1303 catch (final ArgumentException e)
1304 {
1305 debugException(e);
1306 throw new IllegalArgumentException(
1307 ERR_RATE_ADJUSTOR_INVALID_DURATION.get(duration, fullLine,
1308 e.getExceptionMessage()),
1309 e);
1310 }
1311 }
1312
1313 ratesAndDurationList.add(
1314 new ObjectPair<Double,Long>(rate, durationMillis));
1315 }
1316 ratesAndDurations = Collections.unmodifiableList(ratesAndDurationList);
1317 }
1318
1319
1320
1321 /**
1322 * Starts this thread and waits for the initial rate to be set.
1323 */
1324 @Override
1325 public void start()
1326 {
1327 super.start();
1328
1329 // Wait until the initial rate is set. Assuming the caller starts this
1330 // RateAdjustor before the FixedRateBarrier is used by other threads,
1331 // this will guarantee that the initial rate is in place before the
1332 // barrier is used.
1333 try
1334 {
1335 initialRateSetLatch.await();
1336 }
1337 catch (final InterruptedException e)
1338 {
1339 debugException(e);
1340 }
1341 }
1342
1343
1344
1345 /**
1346 * Adjusts the rate in FixedRateBarrier as described in the rates.
1347 */
1348 @Override
1349 public void run()
1350 {
1351 try
1352 {
1353 if (ratesAndDurations.isEmpty())
1354 {
1355 return;
1356 }
1357
1358 do
1359 {
1360 final List<ObjectPair<Double,Long>> ratesAndEndTimes =
1361 new ArrayList<ObjectPair<Double,Long>>(ratesAndDurations.size());
1362 long endTime = System.currentTimeMillis();
1363 for (final ObjectPair<Double,Long> rateAndDuration : ratesAndDurations)
1364 {
1365 endTime += rateAndDuration.getSecond();
1366 ratesAndEndTimes.add(new ObjectPair<Double,Long>(
1367 rateAndDuration.getFirst(), endTime));
1368 }
1369
1370 for (final ObjectPair<Double,Long> rateAndEndTime: ratesAndEndTimes)
1371 {
1372 if (shutDown)
1373 {
1374 return;
1375 }
1376
1377 final double rate = rateAndEndTime.getFirst();
1378 final long intervalMillis = barrier.getTargetRate().getFirst();
1379 final int perInterval = calculatePerInterval(intervalMillis, rate);
1380
1381 barrier.setRate(intervalMillis, perInterval);
1382
1383 // Signal start() that we've set the initial rate.
1384 if (initialRateSetLatch.getCount() > 0)
1385 {
1386 initialRateSetLatch.countDown();
1387 }
1388
1389 // Hold at this rate for the specified duration.
1390 final long durationMillis =
1391 rateAndEndTime.getSecond() - System.currentTimeMillis();
1392 if (durationMillis > 0L)
1393 {
1394 sleeper.sleep(durationMillis);
1395 }
1396 }
1397 }
1398 while (repeat);
1399 }
1400 finally
1401 {
1402 // Just in case we happened to be shutdown before we were started.
1403 // We still want start() to be able to return.
1404 if (initialRateSetLatch.getCount() > 0)
1405 {
1406 initialRateSetLatch.countDown();
1407 }
1408 }
1409 }
1410
1411
1412
1413 /**
1414 * Signals this to shut down.
1415 */
1416 public void shutDown()
1417 {
1418 shutDown = true;
1419 sleeper.wakeup();
1420 }
1421
1422
1423
1424 /**
1425 * Returns the of rates and durations. This is primarily here for testing
1426 * purposes.
1427 *
1428 * @return The list of rates and durations.
1429 */
1430 List<ObjectPair<Double,Long>> getRatesAndDurations()
1431 {
1432 return ratesAndDurations;
1433 }
1434
1435
1436
1437 /**
1438 * Calculates the rate per interval given the specified interval width
1439 * and the target rate per second. (This is static and non-private so that
1440 * it can be unit tested.)
1441 *
1442 * @param intervalDurationMillis The duration of the interval in
1443 * milliseconds.
1444 * @param ratePerSecond The target rate per second.
1445 *
1446 * @return The rate per interval, which will be at least 1.
1447 */
1448 static int calculatePerInterval(final long intervalDurationMillis,
1449 final double ratePerSecond)
1450 {
1451 final double intervalDurationSeconds = intervalDurationMillis / 1000.0;
1452 final double ratePerInterval = ratePerSecond * intervalDurationSeconds;
1453 return (int)Math.max(1, Math.round(ratePerInterval));
1454 }
1455
1456
1457
1458 /**
1459 * This reads the header at the start of the file. All blank lines and
1460 * comment lines will be ignored. The end of the header will be signified by
1461 * a line containing only the text "END HEADER". All non-blank, non-comment
1462 * lines in the header must be in the format "name=value", where there may be
1463 * zero or more spaces on either side of the equal sign, the name must not
1464 * contain either the space or the equal sign character, and the value must
1465 * not begin or end with a space. Header lines must not contain partial-line
1466 * comments.
1467 *
1468 * @param lines The lines of input that include the header.
1469 *
1470 * @return A map of key/value pairs extracted from the header.
1471 *
1472 * @throws IllegalArgumentException If a problem is encountered while
1473 * parsing the header (e.g., a malformed
1474 * header line is encountered, multiple
1475 * headers have the same key, there is no
1476 * end of header marker, etc.).
1477 */
1478 static Map<String,String> consumeHeader(final List<String> lines)
1479 throws IllegalArgumentException
1480 {
1481 // The header will look like this:
1482 // key1=value1
1483 // key2 = value2
1484 // END HEADER
1485 boolean endHeaderFound = false;
1486 final Map<String,String> headerMap = new LinkedHashMap<String,String>(3);
1487 final Iterator<String> lineIter = lines.iterator();
1488 while (lineIter.hasNext())
1489 {
1490 final String line = lineIter.next().trim();
1491 lineIter.remove();
1492
1493 if ((line.length() == 0) ||
1494 line.startsWith(String.valueOf(COMMENT_START)))
1495 {
1496 continue;
1497 }
1498
1499 if (line.equalsIgnoreCase(END_HEADER_TEXT))
1500 {
1501 endHeaderFound = true;
1502 break;
1503 }
1504
1505 final int equalPos = line.indexOf('=');
1506 if (equalPos < 0)
1507 {
1508 throw new IllegalArgumentException(
1509 ERR_RATE_ADJUSTOR_HEADER_NO_EQUAL.get(line));
1510 }
1511
1512 final String key = line.substring(0, equalPos).trim();
1513 if (key.length() == 0)
1514 {
1515 throw new IllegalArgumentException(
1516 ERR_RATE_ADJUSTOR_HEADER_EMPTY_KEY.get(line));
1517 }
1518
1519 final String newValue = line.substring(equalPos+1).trim();
1520 final String existingValue = headerMap.get(key);
1521 if (existingValue != null)
1522 {
1523 throw new IllegalArgumentException(
1524 ERR_RATE_ADJUSTOR_DUPLICATE_HEADER_KEY.get(key, existingValue,
1525 newValue));
1526 }
1527
1528 headerMap.put(key, newValue);
1529 }
1530
1531 if (! endHeaderFound)
1532 {
1533 // This means we iterated across all lines without finding the end header
1534 // marker.
1535 throw new IllegalArgumentException(
1536 ERR_RATE_ADJUSTOR_NO_END_HEADER_FOUND.get(END_HEADER_TEXT));
1537 }
1538
1539 return headerMap;
1540 }
1541
1542
1543
1544 /**
1545 * Returns a list of the lines read from the specified Reader.
1546 *
1547 * @param reader The Reader to read from.
1548 *
1549 * @return A list of the lines read from the specified Reader.
1550 *
1551 * @throws IOException If there is a problem reading from the Reader.
1552 */
1553 private static List<String> readLines(final Reader reader) throws IOException
1554 {
1555 final BufferedReader bufferedReader = new BufferedReader(reader);
1556
1557 // We remove items from the front of the list, so a linked list works best.
1558 final List<String> lines = new LinkedList<String>();
1559
1560 String line;
1561 while ((line = bufferedReader.readLine()) != null)
1562 {
1563 lines.add(line);
1564 }
1565
1566 return lines;
1567 }
1568 }
1569