From 621d222c9435e72cf3a15d9cd9cec35e1c359e0b Mon Sep 17 00:00:00 2001 From: kimchy Date: Tue, 23 Mar 2010 14:20:56 +0200 Subject: [PATCH] Mapping: Support for short type, closes #84. --- .../mapper/json/JsonDocumentMapperParser.java | 16 ++ .../index/mapper/json/JsonMapperBuilders.java | 4 + .../mapper/json/JsonShortFieldMapper.java | 176 ++++++++++++++++++ .../java/org/elasticsearch/util/Numbers.java | 23 +++ .../elasticsearch/util/json/JacksonNodes.java | 8 + 5 files changed, 227 insertions(+) create mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonShortFieldMapper.java diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDocumentMapperParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDocumentMapperParser.java index 51087f9cc3a..c0b8630eb28 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDocumentMapperParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonDocumentMapperParser.java @@ -294,6 +294,8 @@ public class JsonDocumentMapperParser implements DocumentMapperParser { objBuilder.add(parseString(propName, (ObjectNode) propNode)); } else if (type.equals(JsonDateFieldMapper.JSON_TYPE)) { objBuilder.add(parseDate(propName, (ObjectNode) propNode)); + } else if (type.equals(JsonShortFieldMapper.JSON_TYPE)) { + objBuilder.add(parseShort(propName, (ObjectNode) propNode)); } else if (type.equals(JsonIntegerFieldMapper.JSON_TYPE)) { objBuilder.add(parseInteger(propName, (ObjectNode) propNode)); } else if (type.equals(JsonLongFieldMapper.JSON_TYPE)) { @@ -376,6 +378,20 @@ public class JsonDocumentMapperParser implements DocumentMapperParser { return builder; } + private JsonShortFieldMapper.Builder parseShort(String name, ObjectNode integerNode) { + JsonShortFieldMapper.Builder builder = shortField(name); + parseNumberField(builder, name, integerNode); + for (Iterator> propsIt = integerNode.getFields(); propsIt.hasNext();) { + Map.Entry entry = propsIt.next(); + String propName = entry.getKey(); + JsonNode propNode = entry.getValue(); + if (propName.equals("nullValue")) { + builder.nullValue(nodeShortValue(propNode)); + } + } + return builder; + } + private JsonIntegerFieldMapper.Builder parseInteger(String name, ObjectNode integerNode) { JsonIntegerFieldMapper.Builder builder = integerField(name); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonMapperBuilders.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonMapperBuilders.java index 6be0f6f0ea5..ef11b9058a1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonMapperBuilders.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonMapperBuilders.java @@ -80,6 +80,10 @@ public final class JsonMapperBuilders { return new JsonDateFieldMapper.Builder(name); } + public static JsonShortFieldMapper.Builder shortField(String name) { + return new JsonShortFieldMapper.Builder(name); + } + public static JsonIntegerFieldMapper.Builder integerField(String name) { return new JsonIntegerFieldMapper.Builder(name); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonShortFieldMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonShortFieldMapper.java new file mode 100644 index 00000000000..e983bb81aec --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/json/JsonShortFieldMapper.java @@ -0,0 +1,176 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.index.mapper.json; + +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Fieldable; +import org.apache.lucene.search.*; +import org.apache.lucene.util.NumericUtils; +import org.codehaus.jackson.JsonToken; +import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.analysis.NumericIntegerAnalyzer; +import org.elasticsearch.util.Numbers; +import org.elasticsearch.util.json.JsonBuilder; + +import java.io.IOException; + +/** + * @author kimchy (shay.banon) + */ +public class JsonShortFieldMapper extends JsonNumberFieldMapper { + + public static final String JSON_TYPE = "short"; + + public static class Defaults extends JsonNumberFieldMapper.Defaults { + public static final Short NULL_VALUE = null; + } + + public static class Builder extends JsonNumberFieldMapper.Builder { + + protected Short nullValue = Defaults.NULL_VALUE; + + public Builder(String name) { + super(name); + builder = this; + } + + public Builder nullValue(short nullValue) { + this.nullValue = nullValue; + return this; + } + + @Override public JsonShortFieldMapper build(BuilderContext context) { + JsonShortFieldMapper fieldMapper = new JsonShortFieldMapper(buildNames(context), + precisionStep, index, store, boost, omitNorms, omitTermFreqAndPositions, nullValue); + fieldMapper.includeInAll(includeInAll); + return fieldMapper; + } + } + + private final Short nullValue; + + private final String nullValueAsString; + + protected JsonShortFieldMapper(Names names, int precisionStep, Field.Index index, Field.Store store, + float boost, boolean omitNorms, boolean omitTermFreqAndPositions, + Short nullValue) { + super(names, precisionStep, index, store, boost, omitNorms, omitTermFreqAndPositions, + new NamedAnalyzer("_short/" + precisionStep, new NumericIntegerAnalyzer(precisionStep)), + new NamedAnalyzer("_short/max", new NumericIntegerAnalyzer(Integer.MAX_VALUE))); + this.nullValue = nullValue; + this.nullValueAsString = nullValue == null ? null : nullValue.toString(); + } + + @Override protected int maxPrecisionStep() { + return 32; + } + + @Override public Short value(Fieldable field) { + byte[] value = field.getBinaryValue(); + if (value == null) { + return Short.MIN_VALUE; + } + return Numbers.bytesToShort(value); + } + + @Override public String indexedValue(String value) { + return indexedValue(Short.parseShort(value)); + } + + @Override public String indexedValue(Short value) { + return NumericUtils.intToPrefixCoded(value); + } + + @Override public Object valueFromTerm(String term) { + final int shift = term.charAt(0) - NumericUtils.SHIFT_START_INT; + if (shift > 0 && shift <= 31) { + return null; + } + return NumericUtils.prefixCodedToInt(term); + } + + @Override public Object valueFromString(String text) { + return Short.parseShort(text); + } + + @Override public Query rangeQuery(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) { + return NumericRangeQuery.newIntRange(names.indexName(), precisionStep, + lowerTerm == null ? null : Integer.parseInt(lowerTerm), + upperTerm == null ? null : Integer.parseInt(upperTerm), + includeLower, includeUpper); + } + + @Override public Filter rangeFilter(String lowerTerm, String upperTerm, boolean includeLower, boolean includeUpper) { + return NumericRangeFilter.newIntRange(names.indexName(), precisionStep, + lowerTerm == null ? null : Integer.parseInt(lowerTerm), + upperTerm == null ? null : Integer.parseInt(upperTerm), + includeLower, includeUpper); + } + + @Override protected Field parseCreateField(JsonParseContext jsonContext) throws IOException { + int value; + if (jsonContext.jp().getCurrentToken() == JsonToken.VALUE_NULL) { + if (nullValue == null) { + return null; + } + value = nullValue; + if (includeInAll == null || includeInAll) { + jsonContext.allEntries().addText(names.fullName(), nullValueAsString, boost); + } + } else { + if (jsonContext.jp().getCurrentToken() == JsonToken.VALUE_STRING) { + value = Integer.parseInt(jsonContext.jp().getText()); + } else { + value = jsonContext.jp().getIntValue(); + } + if (includeInAll == null || includeInAll) { + jsonContext.allEntries().addText(names.fullName(), jsonContext.jp().getText(), boost); + } + } + Field field = null; + if (stored()) { + field = new Field(names.indexName(), Numbers.shortToBytes(value), store); + if (indexed()) { + field.setTokenStream(popCachedStream(precisionStep).setIntValue(value)); + } + } else if (indexed()) { + field = new Field(names.indexName(), popCachedStream(precisionStep).setIntValue(value)); + } + return field; + } + + @Override public int sortType() { + return SortField.SHORT; + } + + @Override protected String jsonType() { + return JSON_TYPE; + } + + @Override protected void doJsonBody(JsonBuilder builder) throws IOException { + super.doJsonBody(builder); + if (nullValue != null) { + builder.field("nullValue", nullValue); + } + if (includeInAll != null) { + builder.field("includeInAll", includeInAll); + } + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/Numbers.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/Numbers.java index 04e8cf67eb0..aab30c3a9d6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/Numbers.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/Numbers.java @@ -30,6 +30,16 @@ public final class Numbers { } + /** + * Converts a byte array to an short. + * + * @param arr The byte array to convert to an short + * @return The int converted + */ + public static short bytesToShort(byte[] arr) { + return (short) (((arr[2] & 0xff) << 8) | (arr[3] & 0xff)); + } + /** * Converts a byte array to an int. * @@ -87,6 +97,19 @@ public final class Numbers { return arr; } + /** + * Converts an int to a byte array. + * + * @param val The int to convert to a byte array + * @return The byte array converted + */ + public static byte[] shortToBytes(int val) { + byte[] arr = new byte[2]; + arr[2] = (byte) (val >>> 8); + arr[3] = (byte) (val); + return arr; + } + /** * Converts a long to a byte array. * diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JacksonNodes.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JacksonNodes.java index 94ab68cffd7..e0d0cc17701 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JacksonNodes.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JacksonNodes.java @@ -50,6 +50,14 @@ public class JacksonNodes { return Integer.parseInt(value); } + public static short nodeShortValue(JsonNode node) { + if (node.isNumber()) { + return node.getNumberValue().shortValue(); + } + String value = node.getTextValue(); + return Short.parseShort(value); + } + public static long nodeLongValue(JsonNode node) { if (node.isNumber()) { return node.getNumberValue().longValue();