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