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 public Configuration getConf() {
168 return conf.get();
169 }
170
171 /** @param conf the conf to set */
172 public void setConf(Configuration conf) {
173 this.conf.set(conf);
174 }
175
176 /** {@inheritDoc} */
177 public void write(DataOutput out) throws IOException {
178
179 // First write out the size of the class table and any classes that are
180 // "unknown" classes
181
182 out.writeByte(newClasses);
183
184 for (byte i = 1; i <= newClasses; i++) {
185 out.writeByte(i);
186 out.writeUTF(getClass(i).getName());
187 }
188 }
189
190 /** {@inheritDoc} */
191 public void readFields(DataInput in) throws IOException {
192
193 // Get the number of "unknown" classes
194
195 newClasses = in.readByte();
196
197 // Then read in the class names and add them to our tables
198
199 for (int i = 0; i < newClasses; i++) {
200 byte id = in.readByte();
201 String className = in.readUTF();
202 try {
203 addToMap(Class.forName(className), id);
204
205 } catch (ClassNotFoundException e) {
206 throw new IOException("can't find class: " + className + " because "+
207 e.getMessage());
208 }
209 }
210 }
211 }