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.Arrays;
021    import java.util.HashMap;
022    
023    import 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     */
037    public 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    }