001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.util;
019
020import java.util.Arrays;
021import java.util.HashMap;
022
023import com.google.common.base.Preconditions;
024
025/**
026 * Counters for an enum type.
027 * 
028 * For example, suppose there is an enum type
029 * <pre>
030 * enum Fruit { APPLE, ORANGE, GRAPE }
031 * </pre>
032 * An {@link EnumCounters} object can be created for counting the numbers of
033 * APPLE, ORANGLE and GRAPE.
034 *
035 * @param <E> the enum type
036 */
037public class EnumCounters<E extends Enum<E>> {
038  /** The class of the enum. */
039  private final Class<E> enumClass;
040  /** An array of longs corresponding to the enum type. */
041  private final long[] counters;
042
043  /**
044   * Construct counters for the given enum constants.
045   * @param enumClass the enum class of the counters.
046   */
047  public EnumCounters(final Class<E> enumClass) {
048    final E[] enumConstants = enumClass.getEnumConstants();
049    Preconditions.checkNotNull(enumConstants);
050    this.enumClass = enumClass;
051    this.counters = new long[enumConstants.length];
052  }
053  
054  /** @return the value of counter e. */
055  public final long get(final E e) {
056    return counters[e.ordinal()];
057  }
058
059  /** Negate all counters. */
060  public final void negation() {
061    for(int i = 0; i < counters.length; i++) {
062      counters[i] = -counters[i];
063    }
064  }
065  
066  /** Set counter e to the given value. */
067  public final void set(final E e, final long value) {
068    counters[e.ordinal()] = value;
069  }
070
071  /** Set this counters to that counters. */
072  public final void set(final EnumCounters<E> that) {
073    for(int i = 0; i < counters.length; i++) {
074      this.counters[i] = that.counters[i];
075    }
076  }
077
078  /** Reset all counters to zero. */
079  public final void reset() {
080    for(int i = 0; i < counters.length; i++) {
081      this.counters[i] = 0L;
082    }
083  }
084
085  /** Add the given value to counter e. */
086  public final void add(final E e, final long value) {
087    counters[e.ordinal()] += value;
088  }
089
090  /** Add that counters to this counters. */
091  public final void add(final EnumCounters<E> that) {
092    for(int i = 0; i < counters.length; i++) {
093      this.counters[i] += that.counters[i];
094    }
095  }
096
097  /** Subtract the given value from counter e. */
098  public final void subtract(final E e, final long value) {
099    counters[e.ordinal()] -= value;
100  }
101
102  /** Subtract this counters from that counters. */
103  public final void subtract(final EnumCounters<E> that) {
104    for(int i = 0; i < counters.length; i++) {
105      this.counters[i] -= that.counters[i];
106    }
107  }
108  
109  /** @return the sum of all counters. */
110  public final long sum() {
111    long sum = 0;
112    for(int i = 0; i < counters.length; i++) {
113      sum += counters[i];
114    }
115    return sum;
116  }
117
118  @Override
119  public boolean equals(Object obj) {
120    if (obj == this) {
121      return true;
122    } else if (obj == null || !(obj instanceof EnumCounters)) {
123      return false;
124    }
125    final EnumCounters<?> that = (EnumCounters<?>)obj;
126    return this.enumClass == that.enumClass
127        && Arrays.equals(this.counters, that.counters);
128  }
129
130  @Override
131  public int hashCode() {
132    return Arrays.hashCode(counters);
133  }
134
135  @Override
136  public String toString() {
137    final E[] enumConstants = enumClass.getEnumConstants();
138    final StringBuilder b = new StringBuilder();
139    for(int i = 0; i < counters.length; i++) {
140      final String name = enumConstants[i].name();
141      b.append(name).append("=").append(counters[i]).append(", ");
142    }
143    return b.substring(0, b.length() - 2);
144  }
145
146  /**
147   * A factory for creating counters.
148   * 
149   * @param <E> the enum type
150   * @param <C> the counter type
151   */
152  public static interface Factory<E extends Enum<E>,
153                                  C extends EnumCounters<E>> {
154    /** Create a new counters instance. */
155    public C newInstance(); 
156  }
157
158  /**
159   * A key-value map which maps the keys to {@link EnumCounters}.
160   * Note that null key is supported.
161   *
162   * @param <K> the key type
163   * @param <E> the enum type
164   * @param <C> the counter type
165   */
166  public static class Map<K, E extends Enum<E>, C extends EnumCounters<E>> {
167    /** The factory for creating counters. */
168    private final Factory<E, C> factory;
169    /** Key-to-Counts map. */
170    private final java.util.Map<K, C> counts = new HashMap<K, C>();
171    
172    /** Construct a map. */
173    public Map(final Factory<E, C> factory) {
174      this.factory = factory;
175    }
176
177    /** @return the counters for the given key. */
178    public final C getCounts(final K key) {
179      C c = counts.get(key);
180      if (c == null) {
181        c = factory.newInstance();
182        counts.put(key, c); 
183      }
184      return c;
185    }
186    
187    /** @return the sum of the values of all the counters. */
188    public final C sum() {
189      final C sum = factory.newInstance();
190      for(C c : counts.values()) {
191        sum.add(c);
192      }
193      return sum;
194    }
195    
196    /** @return the sum of the values of all the counters for e. */
197    public final long sum(final E e) {
198      long sum = 0;
199      for(C c : counts.values()) {
200        sum += c.get(e);
201      }
202      return sum;
203    }
204
205    @Override
206    public String toString() {
207      return counts.toString();
208    }
209  }
210}