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