001/*
002 * Copyright 2014-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2014-2017 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 */
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.Debug.*;
049import 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)
100public 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      Thread.currentThread().interrupt();
1341    }
1342  }
1343
1344
1345
1346  /**
1347   * Adjusts the rate in FixedRateBarrier as described in the rates.
1348   */
1349  @Override
1350  public void run()
1351  {
1352    try
1353    {
1354      if (ratesAndDurations.isEmpty())
1355      {
1356        return;
1357      }
1358
1359      do
1360      {
1361        final List<ObjectPair<Double,Long>> ratesAndEndTimes =
1362             new ArrayList<ObjectPair<Double,Long>>(ratesAndDurations.size());
1363        long endTime = System.currentTimeMillis();
1364        for (final ObjectPair<Double,Long> rateAndDuration : ratesAndDurations)
1365        {
1366          endTime += rateAndDuration.getSecond();
1367          ratesAndEndTimes.add(new ObjectPair<Double,Long>(
1368               rateAndDuration.getFirst(), endTime));
1369        }
1370
1371        for (final ObjectPair<Double,Long> rateAndEndTime: ratesAndEndTimes)
1372        {
1373          if (shutDown)
1374          {
1375            return;
1376          }
1377
1378          final double rate = rateAndEndTime.getFirst();
1379          final long intervalMillis = barrier.getTargetRate().getFirst();
1380          final int perInterval = calculatePerInterval(intervalMillis, rate);
1381
1382          barrier.setRate(intervalMillis, perInterval);
1383
1384          // Signal start() that we've set the initial rate.
1385          if (initialRateSetLatch.getCount() > 0)
1386          {
1387            initialRateSetLatch.countDown();
1388          }
1389
1390          // Hold at this rate for the specified duration.
1391          final long durationMillis =
1392               rateAndEndTime.getSecond() - System.currentTimeMillis();
1393          if (durationMillis > 0L)
1394          {
1395            sleeper.sleep(durationMillis);
1396          }
1397        }
1398      }
1399      while (repeat);
1400    }
1401    finally
1402    {
1403      // Just in case we happened to be shutdown before we were started.
1404      // We still want start() to be able to return.
1405      if (initialRateSetLatch.getCount() > 0)
1406      {
1407        initialRateSetLatch.countDown();
1408      }
1409    }
1410  }
1411
1412
1413
1414  /**
1415   * Signals this to shut down.
1416   */
1417  public void shutDown()
1418  {
1419    shutDown = true;
1420    sleeper.wakeup();
1421  }
1422
1423
1424
1425  /**
1426   * Returns the of rates and durations.  This is primarily here for testing
1427   * purposes.
1428   *
1429   * @return  The list of rates and durations.
1430   */
1431  List<ObjectPair<Double,Long>> getRatesAndDurations()
1432  {
1433    return ratesAndDurations;
1434  }
1435
1436
1437
1438  /**
1439   * Calculates the rate per interval given the specified interval width
1440   * and the target rate per second.  (This is static and non-private so that
1441   * it can be unit tested.)
1442   *
1443   * @param intervalDurationMillis  The duration of the interval in
1444   *                                milliseconds.
1445   * @param ratePerSecond           The target rate per second.
1446   *
1447   * @return  The rate per interval, which will be at least 1.
1448   */
1449  static int calculatePerInterval(final long intervalDurationMillis,
1450                                  final double ratePerSecond)
1451  {
1452    final double intervalDurationSeconds = intervalDurationMillis / 1000.0;
1453    final double ratePerInterval = ratePerSecond * intervalDurationSeconds;
1454    return (int)Math.max(1, Math.round(ratePerInterval));
1455  }
1456
1457
1458
1459  /**
1460   * This reads the header at the start of the file.  All blank lines and
1461   * comment lines will be ignored.  The end of the header will be signified by
1462   * a line containing only the text "END HEADER".  All non-blank, non-comment
1463   * lines in the header must be in the format "name=value", where there may be
1464   * zero or more spaces on either side of the equal sign, the name must not
1465   * contain either the space or the equal sign character, and the value must
1466   * not begin or end with a space.  Header lines must not contain partial-line
1467   * comments.
1468   *
1469   * @param  lines  The lines of input that include the header.
1470   *
1471   * @return  A map of key/value pairs extracted from the header.
1472   *
1473   * @throws  IllegalArgumentException  If a problem is encountered while
1474   *                                    parsing the header (e.g., a malformed
1475   *                                    header line is encountered, multiple
1476   *                                    headers have the same key, there is no
1477   *                                    end of header marker, etc.).
1478   */
1479  static Map<String,String> consumeHeader(final List<String> lines)
1480         throws IllegalArgumentException
1481  {
1482    // The header will look like this:
1483    // key1=value1
1484    // key2 = value2
1485    // END HEADER
1486    boolean endHeaderFound = false;
1487    final Map<String,String> headerMap = new LinkedHashMap<String,String>(3);
1488    final Iterator<String> lineIter = lines.iterator();
1489    while (lineIter.hasNext())
1490    {
1491      final String line = lineIter.next().trim();
1492      lineIter.remove();
1493
1494      if ((line.length() == 0) ||
1495           line.startsWith(String.valueOf(COMMENT_START)))
1496      {
1497        continue;
1498      }
1499
1500      if (line.equalsIgnoreCase(END_HEADER_TEXT))
1501      {
1502        endHeaderFound = true;
1503        break;
1504      }
1505
1506      final int equalPos = line.indexOf('=');
1507      if (equalPos < 0)
1508      {
1509        throw new IllegalArgumentException(
1510             ERR_RATE_ADJUSTOR_HEADER_NO_EQUAL.get(line));
1511      }
1512
1513      final String key = line.substring(0, equalPos).trim();
1514      if (key.length() == 0)
1515      {
1516        throw new IllegalArgumentException(
1517             ERR_RATE_ADJUSTOR_HEADER_EMPTY_KEY.get(line));
1518      }
1519
1520      final String newValue = line.substring(equalPos+1).trim();
1521      final String existingValue = headerMap.get(key);
1522      if (existingValue != null)
1523      {
1524        throw new IllegalArgumentException(
1525             ERR_RATE_ADJUSTOR_DUPLICATE_HEADER_KEY.get(key, existingValue,
1526                  newValue));
1527      }
1528
1529      headerMap.put(key, newValue);
1530    }
1531
1532    if (! endHeaderFound)
1533    {
1534      // This means we iterated across all lines without finding the end header
1535      // marker.
1536      throw new IllegalArgumentException(
1537           ERR_RATE_ADJUSTOR_NO_END_HEADER_FOUND.get(END_HEADER_TEXT));
1538    }
1539
1540    return headerMap;
1541  }
1542
1543
1544
1545  /**
1546   * Returns a list of the lines read from the specified Reader.
1547   *
1548   * @param  reader  The Reader to read from.
1549   *
1550   * @return  A list of the lines read from the specified Reader.
1551   *
1552   * @throws  IOException  If there is a problem reading from the Reader.
1553   */
1554  private static List<String> readLines(final Reader reader) throws IOException
1555  {
1556    final BufferedReader bufferedReader = new BufferedReader(reader);
1557
1558    // We remove items from the front of the list, so a linked list works best.
1559    final List<String> lines = new LinkedList<String>();
1560
1561    String line;
1562    while ((line = bufferedReader.readLine()) != null)
1563    {
1564      lines.add(line);
1565    }
1566
1567    return lines;
1568  }
1569}
1570