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