package com.ctriposs.baiji.schema; import com.ctriposs.baiji.exception.BaijiRuntimeException; import com.ctriposs.baiji.util.ObjectUtils; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.node.ObjectNode; import java.io.IOException; import java.util.*; public class EnumSchema extends NamedSchema implements Iterable<String> { private final List<String> _symbols; /** * Map of enum symbols and it's corresponding ordinal number * The first element of value is the explicit value which can be null, the second one is the actual value. */ private final Map<String, Integer[]> _symbolMap; /** * Constructor for enum schema * * @param name name of enum * @param doc * @param aliases list of aliases for the name * @param symbols list of enum symbols, Map of enum symbols and it's corresponding ordinal number * The first element of value is the explicit value which can be null, the second one is the actual value. * @param props */ public EnumSchema(SchemaName name, String doc, List<SchemaName> aliases, Map.Entry<String, Integer>[] symbols, PropertyMap props) { super(SchemaType.ENUM, name, doc, aliases, props, new SchemaNames()); if (null == name.getName()) { throw new SchemaParseException("name cannot be null for enum schema."); } _symbols = new ArrayList<String>(); Map<String, Integer[]> symbolMap = new HashMap<String, Integer[]>(); int lastValue = -1; for (Map.Entry<String, Integer> symbol : symbols) { Integer[] values = new Integer[2]; if (symbol.getValue() != null) { values[0] = values[1] = lastValue = symbol.getValue(); } else { values[1] = ++lastValue; } _symbols.add(symbol.getKey()); symbolMap.put(symbol.getKey(), values); } _symbolMap = symbolMap; } /** * Constructor for enum schema * * @param name name of enum * @param doc * @param aliases list of aliases for the name * @param symbols list of enum symbols * @param symbolMap map of enum symbols and value * The first element of value is the explicit value which can be null, the second one is the actual value. * @param props * @param names list of named schema already read */ private EnumSchema(SchemaName name, String doc, List<SchemaName> aliases, List<String> symbols, Map<String, Integer[]> symbolMap, PropertyMap props, SchemaNames names) { super(SchemaType.ENUM, name, doc, aliases, props, names); if (null == name.getName()) { throw new SchemaParseException("name cannot be null for enum schema."); } _symbols = symbols; _symbolMap = symbolMap; } /** * Static function to return new instance of EnumSchema * * @param token JSON object for enum schema * @param props * @param names list of named schema already parsed in * @param encSpace enclosing namespace for the enum schema * @return */ static EnumSchema newInstance(JsonNode token, PropertyMap props, SchemaNames names, String encSpace) { SchemaName name = getName(token, encSpace); List<SchemaName> aliases = getAliases(token, name.getSpace(), name.getEncSpace()); String doc = JsonHelper.getOptionalString(token, "doc"); JsonNode jsymbols = token.get("symbols"); if (jsymbols == null) { throw new SchemaParseException("Enum has no symbols: " + name); } if (!jsymbols.isArray()) { throw new SchemaParseException("symbols field in enum must be an array."); } List<String> symbols = new ArrayList<String>(); Map<String, Integer[]> symbolMap = new HashMap<String, Integer[]>(); int lastValue = -1; for (JsonNode jsymbol : jsymbols) { Integer explicitValue = null; Integer actualValue; String symbol; if (jsymbol.isTextual()) { symbol = jsymbol.getTextValue(); actualValue = ++lastValue; } else if (jsymbol.isObject()) { ObjectNode symbolObj = (ObjectNode) jsymbol; JsonNode symbolNode = symbolObj.get("name"); if (symbolNode == null) { throw new SchemaParseException("Missing symbol name: " + jsymbol); } if (!symbolNode.isTextual()) { throw new SchemaParseException("Symbol name must be a string: " + jsymbol); } symbol = symbolNode.getTextValue(); JsonNode valueNode = symbolObj.get("value"); if (valueNode != null) { if (!valueNode.isInt()) { throw new SchemaParseException("Only integer value is allowed for an enum symbol: " + jsymbol); } explicitValue = valueNode.getIntValue(); } lastValue = actualValue = explicitValue != null ? explicitValue : lastValue + 1; } else { throw new SchemaParseException("Invalid symbol object: " + jsymbol); } if (symbolMap.containsKey(symbol)) { throw new SchemaParseException("Duplicate symbol: " + symbol); } symbolMap.put(symbol, new Integer[]{explicitValue, actualValue}); symbols.add(symbol); } return new EnumSchema(name, doc, aliases, symbols, symbolMap, props, names); } /** * Get count of enum symbols * * @return */ public int size() { return _symbols.size(); } public List<String> getSymbols() { return _symbols; } /** * Returns the position of the given symbol within this enum. * Throws BaijiException if the symbol is not found in this enum. * * @param symbol name of the symbol to find * @return position of the given symbol in this enum schema */ public int ordinal(String symbol) { Integer[] value = _symbolMap.get(symbol); if (value != null) { return value[1].intValue(); } throw new BaijiRuntimeException("No such symbol: " + symbol); } /** * Returns the enum symbol of the given index to the list * * @param value symbol value * @return The symbol name corresponding to the given value. * If there are multiple symbols associated with the given value, the first one in the list will be returned. */ public String get(int value) { for (Map.Entry<String, Integer[]> entry : _symbolMap.entrySet()) { if (entry.getValue()[1] == value) { return entry.getKey(); } } return null; } /** * Checks if given symbol is in the list of enum symbols * * @param symbol symbol to check * @return true if symbol exist, false otherwise */ public boolean contains(String symbol) { return _symbolMap.containsKey(symbol); } /** * Default implementation for writing schema properties in JSON format * * @param gen JSON generator * @param names list of named schemas already written * @param encSpace enclosing namespace of the schema */ protected void writeJsonFields(JsonGenerator gen, SchemaNames names, String encSpace) throws IOException { super.writeJsonFields(gen, names, encSpace); gen.writeFieldName("symbols"); gen.writeStartArray(); for (String s : _symbols) { gen.writeString(s); } gen.writeEndArray(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof EnumSchema)) { return false; } EnumSchema that = (EnumSchema) obj; if (getSchemaName().equals(that.getSchemaName()) && _symbols.size() == that._symbols.size()) { for (int i = 0; i < _symbols.size(); ++i) { if (!_symbols.get(i).equals(that._symbols.get(i))) { return false; } } return ObjectUtils.equals(that.getProps(), getProps()); } return false; } @Override public int hashCode() { int value = (int) (getSchemaName().hashCode() + ObjectUtils.hashCode(getProps())); for (String symbol : _symbols) { value += 23 * symbol.hashCode(); } return value; } @Override public Iterator<String> iterator() { return _symbols.iterator(); } }