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     */
018    package org.apache.hadoop.hdfs.util;
019    
020    import java.util.HashMap;
021    
022    import com.google.common.base.Preconditions;
023    
024    /**
025     * Counters for an enum type.
026     * 
027     * For example, suppose there is an enum type
028     * <pre>
029     * enum Fruit { APPLE, ORANGE, GRAPE }
030     * </pre>
031     * An {@link EnumCounters} object can be created for counting the numbers of
032     * APPLE, ORANGLE and GRAPE.
033     *
034     * @param <E> the enum type
035     */
036    public class EnumCounters<E extends Enum<E>> {
037      /** An array of enum constants. */
038      private final E[] enumConstants;
039      /** The counter array, counters[i] corresponds to the enumConstants[i]. */
040      private final long[] counters;
041    
042      /**
043       * Construct counters for the given enum constants.
044       * @param enumConstants an array of enum constants such that, 
045       *                      for all i, enumConstants[i].ordinal() == i.
046       */
047      public EnumCounters(final E[] enumConstants) {
048        for(int i = 0; i < enumConstants.length; i++) {
049          Preconditions.checkArgument(enumConstants[i].ordinal() == i);
050        }
051        this.enumConstants = enumConstants;
052        this.counters = new long[enumConstants.length];
053      }
054      
055      /** @return the value of counter e. */
056      public final long get(final E e) {
057        return counters[e.ordinal()];
058      }
059    
060      /** Negate all counters. */
061      public final void negation() {
062        for(int i = 0; i < counters.length; i++) {
063          counters[i] = -counters[i];
064        }
065      }
066      
067      /** Set counter e to the given value. */
068      public final void set(final E e, final long value) {
069        counters[e.ordinal()] = value;
070      }
071    
072      /** Add the given value to counter e. */
073      public final void add(final E e, final long value) {
074        counters[e.ordinal()] += value;
075      }
076    
077      /** Add that counters to this counters. */
078      public final void add(final EnumCounters<E> that) {
079        for(int i = 0; i < counters.length; i++) {
080          this.counters[i] += that.counters[i];
081        }
082      }
083    
084      /** Subtract the given value from counter e. */
085      public final void subtract(final E e, final long value) {
086        counters[e.ordinal()] -= value;
087      }
088    
089      /** Subtract that counters from this counters. */
090      public final void subtract(final EnumCounters<E> that) {
091        for(int i = 0; i < counters.length; i++) {
092          this.counters[i] -= that.counters[i];
093        }
094      }
095    
096      @Override
097      public String toString() {
098        final StringBuilder b = new StringBuilder();
099        for(int i = 0; i < counters.length; i++) {
100          final String name = enumConstants[i].name();
101          b.append(name).append("=").append(counters[i]).append(", ");
102        }
103        return b.substring(0, b.length() - 2);
104      }
105    
106      /**
107       * A factory for creating counters.
108       * 
109       * @param <E> the enum type
110       * @param <C> the counter type
111       */
112      public static interface Factory<E extends Enum<E>,
113                                      C extends EnumCounters<E>> {
114        /** Create a new counters instance. */
115        public C newInstance(); 
116      }
117    
118      /**
119       * A key-value map which maps the keys to {@link EnumCounters}.
120       * Note that null key is supported.
121       *
122       * @param <K> the key type
123       * @param <E> the enum type
124       * @param <C> the counter type
125       */
126      public static class Map<K, E extends Enum<E>, C extends EnumCounters<E>> {
127        /** The factory for creating counters. */
128        private final Factory<E, C> factory;
129        /** Key-to-Counts map. */
130        private final java.util.Map<K, C> counts = new HashMap<K, C>();
131        
132        /** Construct a map. */
133        public Map(final Factory<E, C> factory) {
134          this.factory = factory;
135        }
136    
137        /** @return the counters for the given key. */
138        public final C getCounts(final K key) {
139          C c = counts.get(key);
140          if (c == null) {
141            c = factory.newInstance();
142            counts.put(key, c); 
143          }
144          return c;
145        }
146        
147        /** @return the sum of the values of all the counters. */
148        public final C sum() {
149          final C sum = factory.newInstance();
150          for(C c : counts.values()) {
151            sum.add(c);
152          }
153          return sum;
154        }
155        
156        /** @return the sum of the values of all the counters for e. */
157        public final long sum(final E e) {
158          long sum = 0;
159          for(C c : counts.values()) {
160            sum += c.get(e);
161          }
162          return sum;
163        }
164    
165        @Override
166        public String toString() {
167          return counts.toString();
168        }
169      }
170    }