001/*
002 * Copyright 2009-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2019 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util;
022
023
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.List;
029import java.util.Map;
030import java.util.TreeMap;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.atomic.AtomicLong;
033import java.util.concurrent.atomic.AtomicReference;
034
035import com.unboundid.ldap.sdk.ResultCode;
036
037
038
039/**
040 * This class provides a utility that may be used to count operation results and
041 * categorize them based on the total number of results of each type.  It also
042 * provides a method for retrieving result code counts, sorted by the number of
043 * occurrences for each.
044 */
045@Mutable()
046@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
047public final class ResultCodeCounter
048       implements Serializable
049{
050  /**
051   * The serial version UID for this serializable class.
052   */
053  private static final long serialVersionUID = -2280620218815022241L;
054
055
056
057  // The reference to the current map used to hold result code counts.
058  private final AtomicReference<ConcurrentHashMap<ResultCode,AtomicLong>> rcMap;
059
060
061
062  /**
063   * Creates a new instance of this result code counter.
064   */
065  public ResultCodeCounter()
066  {
067    rcMap = new AtomicReference<>();
068    rcMap.set(new ConcurrentHashMap<ResultCode,AtomicLong>(
069         StaticUtils.computeMapCapacity(ResultCode.values().length)));
070  }
071
072
073
074  /**
075   * Increments the count for the provided result code.
076   *
077   * @param  resultCode  The result code for which to increment the count.
078   */
079  public void increment(final ResultCode resultCode)
080  {
081    increment(resultCode, 1);
082  }
083
084
085
086  /**
087   * Increments the count for the provided result code by the specified amount.
088   *
089   * @param  resultCode  The result code for which to increment the count.
090   * @param  amount      The amount by which to increment the count.
091   */
092  public void increment(final ResultCode resultCode, final int amount)
093  {
094    final ConcurrentHashMap<ResultCode,AtomicLong> m = rcMap.get();
095
096    AtomicLong l = m.get(resultCode);
097    if (l == null)
098    {
099      l = new AtomicLong(0L);
100      final AtomicLong l2 = m.putIfAbsent(resultCode, l);
101      if (l2 != null)
102      {
103        l = l2;
104      }
105    }
106
107    l.addAndGet(amount);
108  }
109
110
111
112  /**
113   * Clears all collected data from the result code counter.  Any
114   * previously-collected data will be lost.
115   */
116  public void reset()
117  {
118    rcMap.set(new ConcurrentHashMap<ResultCode,AtomicLong>(
119         StaticUtils.computeMapCapacity(ResultCode.values().length)));
120  }
121
122
123
124  /**
125   * Retrieves a list of the result codes of each type along with their
126   * respective counts.  The returned list will be sorted by number of
127   * occurrences, from most frequent to least frequent.
128   *
129   * @param  reset  Indicates whether to clear the results after obtaining
130   *                them.
131   *
132   * @return  A list of the result codes of each type along with their
133   *          respective counts.
134   */
135  public List<ObjectPair<ResultCode,Long>> getCounts(final boolean reset)
136  {
137    final ConcurrentHashMap<ResultCode,AtomicLong> m;
138    if (reset)
139    {
140      m = rcMap.getAndSet(new ConcurrentHashMap<ResultCode,AtomicLong>(
141           StaticUtils.computeMapCapacity(ResultCode.values().length)));
142    }
143    else
144    {
145      m = new ConcurrentHashMap<>(rcMap.get());
146    }
147
148
149    if (m.isEmpty())
150    {
151      return Collections.emptyList();
152    }
153
154
155    final TreeMap<Long,TreeMap<Integer,ResultCode>> sortedMap =
156         new TreeMap<>(new ReverseComparator<Long>());
157    for (final Map.Entry<ResultCode,AtomicLong> e : m.entrySet())
158    {
159      final long l = e.getValue().longValue();
160      TreeMap<Integer,ResultCode> rcByValue = sortedMap.get(l);
161      if (rcByValue == null)
162      {
163        rcByValue = new TreeMap<>();
164        sortedMap.put(l, rcByValue);
165      }
166
167      final ResultCode rc = e.getKey();
168      rcByValue.put(rc.intValue(), rc);
169    }
170
171
172    final ArrayList<ObjectPair<ResultCode,Long>> rcCounts =
173         new ArrayList<>(2*sortedMap.size());
174    for (final Map.Entry<Long,TreeMap<Integer,ResultCode>> e :
175         sortedMap.entrySet())
176    {
177      final long count = e.getKey();
178      for (final ResultCode rc : e.getValue().values())
179      {
180        rcCounts.add(new ObjectPair<>(rc, count));
181      }
182    }
183
184    return Collections.unmodifiableList(rcCounts);
185  }
186}