PageRenderTime 42ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/release-0.1-rc2/hive/external/serde/src/java/org/apache/hadoop/hive/serde2/binarysortable/BinarySortableSerDe.java

#
Java | 601 lines | 483 code | 36 blank | 82 comment | 91 complexity | 90302c7a8ed143beeb1781bdc7a71cd1 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, JSON, CPL-1.0
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.hadoop.hive.serde2.binarysortable;
  19. import java.io.IOException;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Properties;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.apache.hadoop.conf.Configuration;
  29. import org.apache.hadoop.hive.serde.Constants;
  30. import org.apache.hadoop.hive.serde2.SerDe;
  31. import org.apache.hadoop.hive.serde2.SerDeException;
  32. import org.apache.hadoop.hive.serde2.io.ByteWritable;
  33. import org.apache.hadoop.hive.serde2.io.DoubleWritable;
  34. import org.apache.hadoop.hive.serde2.io.ShortWritable;
  35. import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector;
  36. import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector;
  37. import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
  38. import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
  39. import org.apache.hadoop.hive.serde2.objectinspector.StructField;
  40. import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
  41. import org.apache.hadoop.hive.serde2.objectinspector.UnionObjectInspector;
  42. import org.apache.hadoop.hive.serde2.objectinspector.StandardUnionObjectInspector.StandardUnion;
  43. import org.apache.hadoop.hive.serde2.objectinspector.primitive.BooleanObjectInspector;
  44. import org.apache.hadoop.hive.serde2.objectinspector.primitive.ByteObjectInspector;
  45. import org.apache.hadoop.hive.serde2.objectinspector.primitive.DoubleObjectInspector;
  46. import org.apache.hadoop.hive.serde2.objectinspector.primitive.FloatObjectInspector;
  47. import org.apache.hadoop.hive.serde2.objectinspector.primitive.IntObjectInspector;
  48. import org.apache.hadoop.hive.serde2.objectinspector.primitive.LongObjectInspector;
  49. import org.apache.hadoop.hive.serde2.objectinspector.primitive.ShortObjectInspector;
  50. import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
  51. import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo;
  52. import org.apache.hadoop.hive.serde2.typeinfo.MapTypeInfo;
  53. import org.apache.hadoop.hive.serde2.typeinfo.PrimitiveTypeInfo;
  54. import org.apache.hadoop.hive.serde2.typeinfo.StructTypeInfo;
  55. import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
  56. import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
  57. import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils;
  58. import org.apache.hadoop.hive.serde2.typeinfo.UnionTypeInfo;
  59. import org.apache.hadoop.io.BooleanWritable;
  60. import org.apache.hadoop.io.BytesWritable;
  61. import org.apache.hadoop.io.FloatWritable;
  62. import org.apache.hadoop.io.IntWritable;
  63. import org.apache.hadoop.io.LongWritable;
  64. import org.apache.hadoop.io.Text;
  65. import org.apache.hadoop.io.Writable;
  66. /**
  67. * BinarySortableSerDe can be used to write data in a way that the data can be
  68. * compared byte-by-byte with the same order.
  69. *
  70. * The data format: NULL: a single byte \0 NON-NULL Primitives: ALWAYS prepend a
  71. * single byte \1, and then: Boolean: FALSE = \1, TRUE = \2 Byte: flip the
  72. * sign-bit to make sure negative comes before positive Short: flip the sign-bit
  73. * to make sure negative comes before positive Int: flip the sign-bit to make
  74. * sure negative comes before positive Long: flip the sign-bit to make sure
  75. * negative comes before positive Double: flip the sign-bit for positive double,
  76. * and all bits for negative double values String: NULL-terminated UTF-8 string,
  77. * with NULL escaped to \1 \1, and \1 escaped to \1 \2 NON-NULL Complex Types:
  78. * ALWAYS prepend a single byte \1, and then: Struct: one field by one field.
  79. * List: \1 followed by each element, and \0 to terminate Map: \1 followed by
  80. * each key and then each value, and \0 to terminate
  81. *
  82. * This SerDe takes an additional parameter SERIALIZATION_SORT_ORDER which is a
  83. * string containing only "+" and "-". The length of the string should equal to
  84. * the number of fields in the top-level struct for serialization. "+" means the
  85. * field should be sorted ascendingly, and "-" means descendingly. The sub
  86. * fields in the same top-level field will have the same sort order.
  87. *
  88. */
  89. public class BinarySortableSerDe implements SerDe {
  90. public static final Log LOG = LogFactory.getLog(BinarySortableSerDe.class
  91. .getName());
  92. List<String> columnNames;
  93. List<TypeInfo> columnTypes;
  94. TypeInfo rowTypeInfo;
  95. StructObjectInspector rowObjectInspector;
  96. boolean[] columnSortOrderIsDesc;
  97. @Override
  98. public void initialize(Configuration conf, Properties tbl)
  99. throws SerDeException {
  100. // Get column names and sort order
  101. String columnNameProperty = tbl.getProperty(Constants.LIST_COLUMNS);
  102. String columnTypeProperty = tbl.getProperty(Constants.LIST_COLUMN_TYPES);
  103. if (columnNameProperty.length() == 0) {
  104. columnNames = new ArrayList<String>();
  105. } else {
  106. columnNames = Arrays.asList(columnNameProperty.split(","));
  107. }
  108. if (columnTypeProperty.length() == 0) {
  109. columnTypes = new ArrayList<TypeInfo>();
  110. } else {
  111. columnTypes = TypeInfoUtils
  112. .getTypeInfosFromTypeString(columnTypeProperty);
  113. }
  114. assert (columnNames.size() == columnTypes.size());
  115. // Create row related objects
  116. rowTypeInfo = TypeInfoFactory.getStructTypeInfo(columnNames, columnTypes);
  117. rowObjectInspector = (StructObjectInspector) TypeInfoUtils
  118. .getStandardWritableObjectInspectorFromTypeInfo(rowTypeInfo);
  119. row = new ArrayList<Object>(columnNames.size());
  120. for (int i = 0; i < columnNames.size(); i++) {
  121. row.add(null);
  122. }
  123. // Get the sort order
  124. String columnSortOrder = tbl
  125. .getProperty(Constants.SERIALIZATION_SORT_ORDER);
  126. columnSortOrderIsDesc = new boolean[columnNames.size()];
  127. for (int i = 0; i < columnSortOrderIsDesc.length; i++) {
  128. columnSortOrderIsDesc[i] = (columnSortOrder != null && columnSortOrder
  129. .charAt(i) == '-');
  130. }
  131. }
  132. @Override
  133. public Class<? extends Writable> getSerializedClass() {
  134. return BytesWritable.class;
  135. }
  136. @Override
  137. public ObjectInspector getObjectInspector() throws SerDeException {
  138. return rowObjectInspector;
  139. }
  140. ArrayList<Object> row;
  141. InputByteBuffer inputByteBuffer = new InputByteBuffer();
  142. @Override
  143. public Object deserialize(Writable blob) throws SerDeException {
  144. BytesWritable data = (BytesWritable) blob;
  145. inputByteBuffer.reset(data.get(), 0, data.getSize());
  146. try {
  147. for (int i = 0; i < columnNames.size(); i++) {
  148. row.set(i, deserialize(inputByteBuffer, columnTypes.get(i),
  149. columnSortOrderIsDesc[i], row.get(i)));
  150. }
  151. } catch (IOException e) {
  152. throw new SerDeException(e);
  153. }
  154. return row;
  155. }
  156. static Object deserialize(InputByteBuffer buffer, TypeInfo type,
  157. boolean invert, Object reuse) throws IOException {
  158. // Is this field a null?
  159. byte isNull = buffer.read(invert);
  160. if (isNull == 0) {
  161. return null;
  162. }
  163. assert (isNull == 1);
  164. switch (type.getCategory()) {
  165. case PRIMITIVE: {
  166. PrimitiveTypeInfo ptype = (PrimitiveTypeInfo) type;
  167. switch (ptype.getPrimitiveCategory()) {
  168. case VOID: {
  169. return null;
  170. }
  171. case BOOLEAN: {
  172. BooleanWritable r = reuse == null ? new BooleanWritable()
  173. : (BooleanWritable) reuse;
  174. byte b = buffer.read(invert);
  175. assert (b == 1 || b == 2);
  176. r.set(b == 2);
  177. return r;
  178. }
  179. case BYTE: {
  180. ByteWritable r = reuse == null ? new ByteWritable()
  181. : (ByteWritable) reuse;
  182. r.set((byte) (buffer.read(invert) ^ 0x80));
  183. return r;
  184. }
  185. case SHORT: {
  186. ShortWritable r = reuse == null ? new ShortWritable()
  187. : (ShortWritable) reuse;
  188. int v = buffer.read(invert) ^ 0x80;
  189. v = (v << 8) + (buffer.read(invert) & 0xff);
  190. r.set((short) v);
  191. return r;
  192. }
  193. case INT: {
  194. IntWritable r = reuse == null ? new IntWritable() : (IntWritable) reuse;
  195. int v = buffer.read(invert) ^ 0x80;
  196. for (int i = 0; i < 3; i++) {
  197. v = (v << 8) + (buffer.read(invert) & 0xff);
  198. }
  199. r.set(v);
  200. return r;
  201. }
  202. case LONG: {
  203. LongWritable r = reuse == null ? new LongWritable()
  204. : (LongWritable) reuse;
  205. long v = buffer.read(invert) ^ 0x80;
  206. for (int i = 0; i < 7; i++) {
  207. v = (v << 8) + (buffer.read(invert) & 0xff);
  208. }
  209. r.set(v);
  210. return r;
  211. }
  212. case FLOAT: {
  213. FloatWritable r = reuse == null ? new FloatWritable()
  214. : (FloatWritable) reuse;
  215. int v = 0;
  216. for (int i = 0; i < 4; i++) {
  217. v = (v << 8) + (buffer.read(invert) & 0xff);
  218. }
  219. if ((v & (1 << 31)) == 0) {
  220. // negative number, flip all bits
  221. v = ~v;
  222. } else {
  223. // positive number, flip the first bit
  224. v = v ^ (1 << 31);
  225. }
  226. r.set(Float.intBitsToFloat(v));
  227. return r;
  228. }
  229. case DOUBLE: {
  230. DoubleWritable r = reuse == null ? new DoubleWritable()
  231. : (DoubleWritable) reuse;
  232. long v = 0;
  233. for (int i = 0; i < 8; i++) {
  234. v = (v << 8) + (buffer.read(invert) & 0xff);
  235. }
  236. if ((v & (1L << 63)) == 0) {
  237. // negative number, flip all bits
  238. v = ~v;
  239. } else {
  240. // positive number, flip the first bit
  241. v = v ^ (1L << 63);
  242. }
  243. r.set(Double.longBitsToDouble(v));
  244. return r;
  245. }
  246. case STRING: {
  247. Text r = reuse == null ? new Text() : (Text) reuse;
  248. // Get the actual length first
  249. int start = buffer.tell();
  250. int length = 0;
  251. do {
  252. byte b = buffer.read(invert);
  253. if (b == 0) {
  254. // end of string
  255. break;
  256. }
  257. if (b == 1) {
  258. // the last char is an escape char. read the actual char
  259. buffer.read(invert);
  260. }
  261. length++;
  262. } while (true);
  263. if (length == buffer.tell() - start) {
  264. // No escaping happened, so we are already done.
  265. r.set(buffer.getData(), start, length);
  266. } else {
  267. // Escaping happened, we need to copy byte-by-byte.
  268. // 1. Set the length first.
  269. r.set(buffer.getData(), start, length);
  270. // 2. Reset the pointer.
  271. buffer.seek(start);
  272. // 3. Copy the data.
  273. byte[] rdata = r.getBytes();
  274. for (int i = 0; i < length; i++) {
  275. byte b = buffer.read(invert);
  276. if (b == 1) {
  277. // The last char is an escape char, read the actual char.
  278. // The serialization format escape \0 to \1, and \1 to \2,
  279. // to make sure the string is null-terminated.
  280. b = (byte) (buffer.read(invert) - 1);
  281. }
  282. rdata[i] = b;
  283. }
  284. // 4. Read the null terminator.
  285. byte b = buffer.read(invert);
  286. assert (b == 0);
  287. }
  288. return r;
  289. }
  290. default: {
  291. throw new RuntimeException("Unrecognized type: "
  292. + ptype.getPrimitiveCategory());
  293. }
  294. }
  295. }
  296. case LIST: {
  297. ListTypeInfo ltype = (ListTypeInfo) type;
  298. TypeInfo etype = ltype.getListElementTypeInfo();
  299. // Create the list if needed
  300. ArrayList<Object> r = reuse == null ? new ArrayList<Object>()
  301. : (ArrayList<Object>) reuse;
  302. // Read the list
  303. int size = 0;
  304. while (true) {
  305. int more = buffer.read(invert);
  306. if (more == 0) {
  307. // \0 to terminate
  308. break;
  309. }
  310. // \1 followed by each element
  311. assert (more == 1);
  312. if (size == r.size()) {
  313. r.add(null);
  314. }
  315. r.set(size, deserialize(buffer, etype, invert, r.get(size)));
  316. size++;
  317. }
  318. // Remove additional elements if the list is reused
  319. while (r.size() > size) {
  320. r.remove(r.size() - 1);
  321. }
  322. return r;
  323. }
  324. case MAP: {
  325. MapTypeInfo mtype = (MapTypeInfo) type;
  326. TypeInfo ktype = mtype.getMapKeyTypeInfo();
  327. TypeInfo vtype = mtype.getMapValueTypeInfo();
  328. // Create the map if needed
  329. Map<Object, Object> r;
  330. if (reuse == null) {
  331. r = new HashMap<Object, Object>();
  332. } else {
  333. r = (HashMap<Object, Object>) reuse;
  334. r.clear();
  335. }
  336. while (true) {
  337. int more = buffer.read(invert);
  338. if (more == 0) {
  339. // \0 to terminate
  340. break;
  341. }
  342. // \1 followed by each key and then each value
  343. assert (more == 1);
  344. Object k = deserialize(buffer, ktype, invert, null);
  345. Object v = deserialize(buffer, vtype, invert, null);
  346. r.put(k, v);
  347. }
  348. return r;
  349. }
  350. case STRUCT: {
  351. StructTypeInfo stype = (StructTypeInfo) type;
  352. List<TypeInfo> fieldTypes = stype.getAllStructFieldTypeInfos();
  353. int size = fieldTypes.size();
  354. // Create the struct if needed
  355. ArrayList<Object> r = reuse == null ? new ArrayList<Object>(size)
  356. : (ArrayList<Object>) reuse;
  357. assert (r.size() <= size);
  358. // Set the size of the struct
  359. while (r.size() < size) {
  360. r.add(null);
  361. }
  362. // Read one field by one field
  363. for (int eid = 0; eid < size; eid++) {
  364. r
  365. .set(eid, deserialize(buffer, fieldTypes.get(eid), invert, r
  366. .get(eid)));
  367. }
  368. return r;
  369. }
  370. case UNION: {
  371. UnionTypeInfo utype = (UnionTypeInfo) type;
  372. StandardUnion r = reuse == null ? new StandardUnion()
  373. : (StandardUnion) reuse;
  374. // Read the tag
  375. byte tag = buffer.read(invert);
  376. r.setTag(tag);
  377. r.setObject(deserialize(buffer, utype.getAllUnionObjectTypeInfos().get(tag),
  378. invert, null));
  379. return r;
  380. }
  381. default: {
  382. throw new RuntimeException("Unrecognized type: " + type.getCategory());
  383. }
  384. }
  385. }
  386. BytesWritable serializeBytesWritable = new BytesWritable();
  387. OutputByteBuffer outputByteBuffer = new OutputByteBuffer();
  388. @Override
  389. public Writable serialize(Object obj, ObjectInspector objInspector) throws SerDeException {
  390. outputByteBuffer.reset();
  391. StructObjectInspector soi = (StructObjectInspector) objInspector;
  392. List<? extends StructField> fields = soi.getAllStructFieldRefs();
  393. for (int i = 0; i < columnNames.size(); i++) {
  394. serialize(outputByteBuffer, soi.getStructFieldData(obj, fields.get(i)),
  395. fields.get(i).getFieldObjectInspector(), columnSortOrderIsDesc[i]);
  396. }
  397. serializeBytesWritable.set(outputByteBuffer.getData(), 0, outputByteBuffer
  398. .getLength());
  399. return serializeBytesWritable;
  400. }
  401. static void serialize(OutputByteBuffer buffer, Object o, ObjectInspector oi,
  402. boolean invert) {
  403. // Is this field a null?
  404. if (o == null) {
  405. buffer.write((byte) 0, invert);
  406. return;
  407. }
  408. // This field is not a null.
  409. buffer.write((byte) 1, invert);
  410. switch (oi.getCategory()) {
  411. case PRIMITIVE: {
  412. PrimitiveObjectInspector poi = (PrimitiveObjectInspector) oi;
  413. switch (poi.getPrimitiveCategory()) {
  414. case VOID: {
  415. return;
  416. }
  417. case BOOLEAN: {
  418. boolean v = ((BooleanObjectInspector) poi).get(o);
  419. buffer.write((byte) (v ? 2 : 1), invert);
  420. return;
  421. }
  422. case BYTE: {
  423. ByteObjectInspector boi = (ByteObjectInspector) poi;
  424. byte v = boi.get(o);
  425. buffer.write((byte) (v ^ 0x80), invert);
  426. return;
  427. }
  428. case SHORT: {
  429. ShortObjectInspector spoi = (ShortObjectInspector) poi;
  430. short v = spoi.get(o);
  431. buffer.write((byte) ((v >> 8) ^ 0x80), invert);
  432. buffer.write((byte) v, invert);
  433. return;
  434. }
  435. case INT: {
  436. IntObjectInspector ioi = (IntObjectInspector) poi;
  437. int v = ioi.get(o);
  438. buffer.write((byte) ((v >> 24) ^ 0x80), invert);
  439. buffer.write((byte) (v >> 16), invert);
  440. buffer.write((byte) (v >> 8), invert);
  441. buffer.write((byte) v, invert);
  442. return;
  443. }
  444. case LONG: {
  445. LongObjectInspector loi = (LongObjectInspector) poi;
  446. long v = loi.get(o);
  447. buffer.write((byte) ((v >> 56) ^ 0x80), invert);
  448. buffer.write((byte) (v >> 48), invert);
  449. buffer.write((byte) (v >> 40), invert);
  450. buffer.write((byte) (v >> 32), invert);
  451. buffer.write((byte) (v >> 24), invert);
  452. buffer.write((byte) (v >> 16), invert);
  453. buffer.write((byte) (v >> 8), invert);
  454. buffer.write((byte) v, invert);
  455. return;
  456. }
  457. case FLOAT: {
  458. FloatObjectInspector foi = (FloatObjectInspector) poi;
  459. int v = Float.floatToIntBits(foi.get(o));
  460. if ((v & (1 << 31)) != 0) {
  461. // negative number, flip all bits
  462. v = ~v;
  463. } else {
  464. // positive number, flip the first bit
  465. v = v ^ (1 << 31);
  466. }
  467. buffer.write((byte) (v >> 24), invert);
  468. buffer.write((byte) (v >> 16), invert);
  469. buffer.write((byte) (v >> 8), invert);
  470. buffer.write((byte) v, invert);
  471. return;
  472. }
  473. case DOUBLE: {
  474. DoubleObjectInspector doi = (DoubleObjectInspector) poi;
  475. long v = Double.doubleToLongBits(doi.get(o));
  476. if ((v & (1L << 63)) != 0) {
  477. // negative number, flip all bits
  478. v = ~v;
  479. } else {
  480. // positive number, flip the first bit
  481. v = v ^ (1L << 63);
  482. }
  483. buffer.write((byte) (v >> 56), invert);
  484. buffer.write((byte) (v >> 48), invert);
  485. buffer.write((byte) (v >> 40), invert);
  486. buffer.write((byte) (v >> 32), invert);
  487. buffer.write((byte) (v >> 24), invert);
  488. buffer.write((byte) (v >> 16), invert);
  489. buffer.write((byte) (v >> 8), invert);
  490. buffer.write((byte) v, invert);
  491. return;
  492. }
  493. case STRING: {
  494. StringObjectInspector soi = (StringObjectInspector) poi;
  495. Text t = soi.getPrimitiveWritableObject(o);
  496. byte[] data = t.getBytes();
  497. int length = t.getLength();
  498. for (int i = 0; i < length; i++) {
  499. if (data[i] == 0 || data[i] == 1) {
  500. buffer.write((byte) 1, invert);
  501. buffer.write((byte) (data[i] + 1), invert);
  502. } else {
  503. buffer.write(data[i], invert);
  504. }
  505. }
  506. buffer.write((byte) 0, invert);
  507. return;
  508. }
  509. default: {
  510. throw new RuntimeException("Unrecognized type: "
  511. + poi.getPrimitiveCategory());
  512. }
  513. }
  514. }
  515. case LIST: {
  516. ListObjectInspector loi = (ListObjectInspector) oi;
  517. ObjectInspector eoi = loi.getListElementObjectInspector();
  518. // \1 followed by each element
  519. int size = loi.getListLength(o);
  520. for (int eid = 0; eid < size; eid++) {
  521. buffer.write((byte) 1, invert);
  522. serialize(buffer, loi.getListElement(o, eid), eoi, invert);
  523. }
  524. // and \0 to terminate
  525. buffer.write((byte) 0, invert);
  526. return;
  527. }
  528. case MAP: {
  529. MapObjectInspector moi = (MapObjectInspector) oi;
  530. ObjectInspector koi = moi.getMapKeyObjectInspector();
  531. ObjectInspector voi = moi.getMapValueObjectInspector();
  532. // \1 followed by each key and then each value
  533. Map<?, ?> map = moi.getMap(o);
  534. for (Map.Entry<?, ?> entry : map.entrySet()) {
  535. buffer.write((byte) 1, invert);
  536. serialize(buffer, entry.getKey(), koi, invert);
  537. serialize(buffer, entry.getValue(), voi, invert);
  538. }
  539. // and \0 to terminate
  540. buffer.write((byte) 0, invert);
  541. return;
  542. }
  543. case STRUCT: {
  544. StructObjectInspector soi = (StructObjectInspector) oi;
  545. List<? extends StructField> fields = soi.getAllStructFieldRefs();
  546. for (int i = 0; i < fields.size(); i++) {
  547. serialize(buffer, soi.getStructFieldData(o, fields.get(i)), fields.get(
  548. i).getFieldObjectInspector(), invert);
  549. }
  550. return;
  551. }
  552. case UNION: {
  553. UnionObjectInspector uoi = (UnionObjectInspector) oi;
  554. byte tag = uoi.getTag(o);
  555. buffer.write(tag, invert);
  556. serialize(buffer, uoi.getField(o), uoi.getObjectInspectors().get(tag),
  557. invert);
  558. return;
  559. }
  560. default: {
  561. throw new RuntimeException("Unrecognized type: " + oi.getCategory());
  562. }
  563. }
  564. }
  565. }