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