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