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.io;
019    
020    import java.io.DataInput;
021    import java.io.DataOutput;
022    import java.io.IOException;
023    import java.util.Map;
024    import java.util.concurrent.ConcurrentHashMap;
025    import java.util.concurrent.atomic.AtomicReference;
026    
027    import org.apache.hadoop.classification.InterfaceAudience;
028    import org.apache.hadoop.classification.InterfaceStability;
029    import org.apache.hadoop.conf.Configurable;
030    import org.apache.hadoop.conf.Configuration;
031    
032    /**
033     * Abstract base class for MapWritable and SortedMapWritable
034     * 
035     * Unlike org.apache.nutch.crawl.MapWritable, this class allows creation of
036     * MapWritable<Writable, MapWritable> so the CLASS_TO_ID and ID_TO_CLASS
037     * maps travel with the class instead of being static.
038     * 
039     * Class ids range from 1 to 127 so there can be at most 127 distinct classes
040     * in any specific map instance.
041     */
042    @InterfaceAudience.Public
043    @InterfaceStability.Stable
044    public abstract class AbstractMapWritable implements Writable, Configurable {
045      private AtomicReference<Configuration> conf;
046      
047      /* Class to id mappings */
048      private Map<Class, Byte> classToIdMap = new ConcurrentHashMap<Class, Byte>();
049      
050      /* Id to Class mappings */
051      private Map<Byte, Class> idToClassMap = new ConcurrentHashMap<Byte, Class>();
052      
053      /* The number of new classes (those not established by the constructor) */
054      private volatile byte newClasses = 0;
055      
056      /** @return the number of known classes */
057      byte getNewClasses() {
058        return newClasses;
059      }
060    
061      /**
062       * Used to add "predefined" classes and by Writable to copy "new" classes.
063       */
064      private synchronized void addToMap(Class clazz, byte id) {
065        if (classToIdMap.containsKey(clazz)) {
066          byte b = classToIdMap.get(clazz);
067          if (b != id) {
068            throw new IllegalArgumentException ("Class " + clazz.getName() +
069              " already registered but maps to " + b + " and not " + id);
070          }
071        }
072        if (idToClassMap.containsKey(id)) {
073          Class c = idToClassMap.get(id);
074          if (!c.equals(clazz)) {
075            throw new IllegalArgumentException("Id " + id + " exists but maps to " +
076                c.getName() + " and not " + clazz.getName());
077          }
078        }
079        classToIdMap.put(clazz, id);
080        idToClassMap.put(id, clazz);
081      }
082      
083      /** Add a Class to the maps if it is not already present. */ 
084      protected synchronized void addToMap(Class clazz) {
085        if (classToIdMap.containsKey(clazz)) {
086          return;
087        }
088        if (newClasses + 1 > Byte.MAX_VALUE) {
089          throw new IndexOutOfBoundsException("adding an additional class would" +
090          " exceed the maximum number allowed");
091        }
092        byte id = ++newClasses;
093        addToMap(clazz, id);
094      }
095    
096      /** @return the Class class for the specified id */
097      protected Class getClass(byte id) {
098        return idToClassMap.get(id);
099      }
100    
101      /** @return the id for the specified Class */
102      protected byte getId(Class clazz) {
103        return classToIdMap.containsKey(clazz) ? classToIdMap.get(clazz) : -1;
104      }
105    
106      /** Used by child copy constructors. */
107      protected synchronized void copy(Writable other) {
108        if (other != null) {
109          try {
110            DataOutputBuffer out = new DataOutputBuffer();
111            other.write(out);
112            DataInputBuffer in = new DataInputBuffer();
113            in.reset(out.getData(), out.getLength());
114            readFields(in);
115            
116          } catch (IOException e) {
117            throw new IllegalArgumentException("map cannot be copied: " +
118                e.getMessage());
119          }
120          
121        } else {
122          throw new IllegalArgumentException("source map cannot be null");
123        }
124      }
125      
126      /** constructor. */
127      protected AbstractMapWritable() {
128        this.conf = new AtomicReference<Configuration>();
129    
130        addToMap(ArrayWritable.class,
131            Byte.valueOf(Integer.valueOf(-127).byteValue())); 
132        addToMap(BooleanWritable.class,
133            Byte.valueOf(Integer.valueOf(-126).byteValue()));
134        addToMap(BytesWritable.class,
135            Byte.valueOf(Integer.valueOf(-125).byteValue()));
136        addToMap(FloatWritable.class,
137            Byte.valueOf(Integer.valueOf(-124).byteValue()));
138        addToMap(IntWritable.class,
139            Byte.valueOf(Integer.valueOf(-123).byteValue()));
140        addToMap(LongWritable.class,
141            Byte.valueOf(Integer.valueOf(-122).byteValue()));
142        addToMap(MapWritable.class,
143            Byte.valueOf(Integer.valueOf(-121).byteValue()));
144        addToMap(MD5Hash.class,
145            Byte.valueOf(Integer.valueOf(-120).byteValue()));
146        addToMap(NullWritable.class,
147            Byte.valueOf(Integer.valueOf(-119).byteValue()));
148        addToMap(ObjectWritable.class,
149            Byte.valueOf(Integer.valueOf(-118).byteValue()));
150        addToMap(SortedMapWritable.class,
151            Byte.valueOf(Integer.valueOf(-117).byteValue()));
152        addToMap(Text.class,
153            Byte.valueOf(Integer.valueOf(-116).byteValue()));
154        addToMap(TwoDArrayWritable.class,
155            Byte.valueOf(Integer.valueOf(-115).byteValue()));
156        
157        // UTF8 is deprecated so we don't support it
158    
159        addToMap(VIntWritable.class,
160            Byte.valueOf(Integer.valueOf(-114).byteValue()));
161        addToMap(VLongWritable.class,
162            Byte.valueOf(Integer.valueOf(-113).byteValue()));
163    
164      }
165    
166      /** @return the conf */
167      @Override
168      public Configuration getConf() {
169        return conf.get();
170      }
171    
172      /** @param conf the conf to set */
173      @Override
174      public void setConf(Configuration conf) {
175        this.conf.set(conf);
176      }
177      
178      @Override
179      public void write(DataOutput out) throws IOException {
180        
181        // First write out the size of the class table and any classes that are
182        // "unknown" classes
183        
184        out.writeByte(newClasses);
185    
186        for (byte i = 1; i <= newClasses; i++) {
187          out.writeByte(i);
188          out.writeUTF(getClass(i).getName());
189        }
190      }
191      
192      @Override
193      public void readFields(DataInput in) throws IOException {
194        
195        // Get the number of "unknown" classes
196        
197        newClasses = in.readByte();
198        
199        // Then read in the class names and add them to our tables
200        
201        for (int i = 0; i < newClasses; i++) {
202          byte id = in.readByte();
203          String className = in.readUTF();
204          try {
205            addToMap(Class.forName(className), id);
206            
207          } catch (ClassNotFoundException e) {
208            throw new IOException("can't find class: " + className + " because "+
209                e.getMessage());
210          }
211        }
212      }    
213    }