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  /** The counter array, counters[i] corresponds to the enumConstants[i]. */
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  /** Add the given value to counter e. */
079  public final void add(final E e, final long value) {
080    counters[e.ordinal()] += value;
081  }
082
083  /** Add that counters to this counters. */
084  public final void add(final EnumCounters<E> that) {
085    for(int i = 0; i < counters.length; i++) {
086      this.counters[i] += that.counters[i];
087    }
088  }
089
090  /** Subtract the given value from counter e. */
091  public final void subtract(final E e, final long value) {
092    counters[e.ordinal()] -= value;
093  }
094
095  /** Subtract this counters from that counters. */
096  public final void subtract(final EnumCounters<E> that) {
097    for(int i = 0; i < counters.length; i++) {
098      this.counters[i] -= that.counters[i];
099    }
100  }
101
102  @Override
103  public boolean equals(Object obj) {
104    if (obj == this) {
105      return true;
106    } else if (obj == null || !(obj instanceof EnumCounters)) {
107      return false;
108    }
109    final EnumCounters<?> that = (EnumCounters<?>)obj;
110    return this.enumClass == that.enumClass
111        && Arrays.equals(this.counters, that.counters);
112  }
113
114  @Override
115  public int hashCode() {
116    return Arrays.hashCode(counters);
117  }
118
119  @Override
120  public String toString() {
121    final E[] enumConstants = enumClass.getEnumConstants();
122    final StringBuilder b = new StringBuilder();
123    for(int i = 0; i < counters.length; i++) {
124      final String name = enumConstants[i].name();
125      b.append(name).append("=").append(counters[i]).append(", ");
126    }
127    return b.substring(0, b.length() - 2);
128  }
129
130  /**
131   * A factory for creating counters.
132   * 
133   * @param <E> the enum type
134   * @param <C> the counter type
135   */
136  public static interface Factory<E extends Enum<E>,
137                                  C extends EnumCounters<E>> {
138    /** Create a new counters instance. */
139    public C newInstance(); 
140  }
141
142  /**
143   * A key-value map which maps the keys to {@link EnumCounters}.
144   * Note that null key is supported.
145   *
146   * @param <K> the key type
147   * @param <E> the enum type
148   * @param <C> the counter type
149   */
150  public static class Map<K, E extends Enum<E>, C extends EnumCounters<E>> {
151    /** The factory for creating counters. */
152    private final Factory<E, C> factory;
153    /** Key-to-Counts map. */
154    private final java.util.Map<K, C> counts = new HashMap<K, C>();
155    
156    /** Construct a map. */
157    public Map(final Factory<E, C> factory) {
158      this.factory = factory;
159    }
160
161    /** @return the counters for the given key. */
162    public final C getCounts(final K key) {
163      C c = counts.get(key);
164      if (c == null) {
165        c = factory.newInstance();
166        counts.put(key, c); 
167      }
168      return c;
169    }
170    
171    /** @return the sum of the values of all the counters. */
172    public final C sum() {
173      final C sum = factory.newInstance();
174      for(C c : counts.values()) {
175        sum.add(c);
176      }
177      return sum;
178    }
179    
180    /** @return the sum of the values of all the counters for e. */
181    public final long sum(final E e) {
182      long sum = 0;
183      for(C c : counts.values()) {
184        sum += c.get(e);
185      }
186      return sum;
187    }
188
189    @Override
190    public String toString() {
191      return counts.toString();
192    }
193  }
194}