/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.cassandra.db.marshal;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.*;

import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;

import org.apache.cassandra.cql3.FieldIdentifier;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;

Parse a string containing an Type definition.
/** * Parse a string containing an Type definition. */
public class TypeParser { private final String str; private int idx; // A cache of parsed string, specially useful for DynamicCompositeType private static volatile ImmutableMap<String, AbstractType<?>> cache = ImmutableMap.of(); public static final TypeParser EMPTY_PARSER = new TypeParser("", 0); private TypeParser(String str, int idx) { this.str = str; this.idx = idx; } public TypeParser(String str) { this(str, 0); }
Parse a string containing an type definition.
/** * Parse a string containing an type definition. */
public static AbstractType<?> parse(String str) throws SyntaxException, ConfigurationException { if (str == null) return BytesType.instance; // A single volatile read of 'cache' should not hurt. AbstractType<?> type = cache.get(str); if (type != null) return type; // This could be simplier (i.e. new TypeParser(str).parse()) but we avoid creating a TypeParser object if not really necessary. int i = 0; i = skipBlank(str, i); int j = i; while (!isEOS(str, i) && isIdentifierChar(str.charAt(i))) ++i; if (i == j) return BytesType.instance; String name = str.substring(j, i); i = skipBlank(str, i); if (!isEOS(str, i) && str.charAt(i) == '(') type = getAbstractType(name, new TypeParser(str, i)); else type = getAbstractType(name); Verify.verify(type != null, "Parsing %s yielded null, which is a bug", str); // Prevent concurrent modification to the map acting as the cache for TypeParser at the expense of // more allocation when the cache needs to be updated, since updates to the cache are rare compared // to the amount of reads. // // Copy the existing cache into a new map and add the parsed AbstractType instance and replace // the cache, if the type is not already in the cache. // // The cache-update is done in a short synchronized block to prevent duplicate instances of AbstractType // for the same string representation. synchronized (TypeParser.class) { if (!cache.containsKey(str)) { ImmutableMap.Builder<String, AbstractType<?>> builder = ImmutableMap.builder(); builder.putAll(cache).put(str, type); cache = builder.build(); } return type; } } public static AbstractType<?> parse(CharSequence compareWith) throws SyntaxException, ConfigurationException { return parse(compareWith == null ? null : compareWith.toString()); }
Parse an AbstractType from current position of this parser.
/** * Parse an AbstractType from current position of this parser. */
public AbstractType<?> parse() throws SyntaxException, ConfigurationException { skipBlank(); String name = readNextIdentifier(); skipBlank(); if (!isEOS() && str.charAt(idx) == '(') return getAbstractType(name, this); else return getAbstractType(name); } public Map<String, String> getKeyValueParameters() throws SyntaxException { if (isEOS()) return Collections.emptyMap(); if (str.charAt(idx) != '(') throw new IllegalStateException(); Map<String, String> map = new HashMap<>(); ++idx; // skipping '(' while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return map; } String k = readNextIdentifier(); String v = ""; skipBlank(); if (str.charAt(idx) == '=') { ++idx; skipBlank(); v = readNextIdentifier(); } else if (str.charAt(idx) != ',' && str.charAt(idx) != ')') { throwSyntaxError("unexpected character '" + str.charAt(idx) + "'"); } map.put(k, v); } throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } public List<AbstractType<?>> getTypeParameters() throws SyntaxException, ConfigurationException { List<AbstractType<?>> list = new ArrayList<>(); if (isEOS()) return list; if (str.charAt(idx) != '(') throw new IllegalStateException(); ++idx; // skipping '(' while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return list; } try { list.add(parse()); } catch (SyntaxException e) { SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", str, idx)); ex.initCause(e); throw ex; } } throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } public Map<Byte, AbstractType<?>> getAliasParameters() throws SyntaxException, ConfigurationException { Map<Byte, AbstractType<?>> map = new HashMap<>(); if (isEOS()) return map; if (str.charAt(idx) != '(') throw new IllegalStateException(); ++idx; // skipping '(' while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return map; } String alias = readNextIdentifier(); if (alias.length() != 1) throwSyntaxError("An alias should be a single character"); char aliasChar = alias.charAt(0); if (aliasChar < 33 || aliasChar > 127) throwSyntaxError("An alias should be a single character in [0..9a..bA..B-+._&]"); skipBlank(); if (!(str.charAt(idx) == '=' && str.charAt(idx+1) == '>')) throwSyntaxError("expecting '=>' token"); idx += 2; skipBlank(); try { map.put((byte)aliasChar, parse()); } catch (SyntaxException e) { SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", str, idx)); ex.initCause(e); throw ex; } } throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } public Map<ByteBuffer, CollectionType> getCollectionsParameters() throws SyntaxException, ConfigurationException { Map<ByteBuffer, CollectionType> map = new HashMap<>(); if (isEOS()) return map; if (str.charAt(idx) != '(') throw new IllegalStateException(); ++idx; // skipping '(' while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return map; } ByteBuffer bb = fromHex(readNextIdentifier()); skipBlank(); if (str.charAt(idx) != ':') throwSyntaxError("expecting ':' token"); ++idx; skipBlank(); try { AbstractType<?> type = parse(); if (!(type instanceof CollectionType)) throw new SyntaxException(type + " is not a collection type"); map.put(bb, (CollectionType)type); } catch (SyntaxException e) { SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", str, idx)); ex.initCause(e); throw ex; } } throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } private ByteBuffer fromHex(String hex) throws SyntaxException { try { return ByteBufferUtil.hexToBytes(hex); } catch (NumberFormatException e) { throwSyntaxError(e.getMessage()); return null; } } public Pair<Pair<String, ByteBuffer>, List<Pair<ByteBuffer, AbstractType>>> getUserTypeParameters() throws SyntaxException, ConfigurationException { if (isEOS() || str.charAt(idx) != '(') throw new IllegalStateException(); ++idx; // skipping '(' skipBlankAndComma(); String keyspace = readNextIdentifier(); skipBlankAndComma(); ByteBuffer typeName = fromHex(readNextIdentifier()); List<Pair<ByteBuffer, AbstractType>> defs = new ArrayList<>(); while (skipBlankAndComma()) { if (str.charAt(idx) == ')') { ++idx; return Pair.create(Pair.create(keyspace, typeName), defs); } ByteBuffer name = fromHex(readNextIdentifier()); skipBlank(); if (str.charAt(idx) != ':') throwSyntaxError("expecting ':' token"); ++idx; skipBlank(); try { AbstractType type = parse(); defs.add(Pair.create(name, type)); } catch (SyntaxException e) { SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", str, idx)); ex.initCause(e); throw ex; } } throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", str, idx)); } private static AbstractType<?> getAbstractType(String compareWith) throws ConfigurationException { String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith; Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type"); try { Field field = typeClass.getDeclaredField("instance"); return (AbstractType<?>) field.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { // Trying with empty parser return getRawAbstractType(typeClass, EMPTY_PARSER); } } private static AbstractType<?> getAbstractType(String compareWith, TypeParser parser) throws SyntaxException, ConfigurationException { String className = compareWith.contains(".") ? compareWith : "org.apache.cassandra.db.marshal." + compareWith; Class<? extends AbstractType<?>> typeClass = FBUtilities.<AbstractType<?>>classForName(className, "abstract-type"); try { Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); return (AbstractType<?>) method.invoke(null, parser); } catch (NoSuchMethodException | IllegalAccessException e) { // Trying to see if we have an instance field and apply the default parameter to it AbstractType<?> type = getRawAbstractType(typeClass); return AbstractType.parseDefaultParameters(type, parser); } catch (InvocationTargetException e) { ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); ex.initCause(e.getTargetException()); throw ex; } } private static AbstractType<?> getRawAbstractType(Class<? extends AbstractType<?>> typeClass) throws ConfigurationException { try { Field field = typeClass.getDeclaredField("instance"); return (AbstractType<?>) field.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); } } private static AbstractType<?> getRawAbstractType(Class<? extends AbstractType<?>> typeClass, TypeParser parser) throws ConfigurationException { try { Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); return (AbstractType<?>) method.invoke(null, parser); } catch (NoSuchMethodException | IllegalAccessException e) { throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); } catch (InvocationTargetException e) { ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); ex.initCause(e.getTargetException()); throw ex; } } private void throwSyntaxError(String msg) throws SyntaxException { throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: %s", str, idx, msg)); } private boolean isEOS() { return isEOS(str, idx); } private static boolean isEOS(String str, int i) { return i >= str.length(); } private static boolean isBlank(int c) { return c == ' ' || c == '\t' || c == '\n'; } private void skipBlank() { idx = skipBlank(str, idx); } private static int skipBlank(String str, int i) { while (!isEOS(str, i) && isBlank(str.charAt(i))) ++i; return i; } // skip all blank and at best one comma, return true if there not EOS private boolean skipBlankAndComma() { boolean commaFound = false; while (!isEOS()) { int c = str.charAt(idx); if (c == ',') { if (commaFound) return true; else commaFound = true; } else if (!isBlank(c)) { return true; } ++idx; } return false; } /* * [0..9a..bA..B-+._&] */ private static boolean isIdentifierChar(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '+' || c == '.' || c == '_' || c == '&'; } // left idx positioned on the character stopping the read public String readNextIdentifier() { int i = idx; while (!isEOS() && isIdentifierChar(str.charAt(idx))) ++idx; return str.substring(i, idx); }
Helper function to ease the writing of AbstractType.toString() methods.
/** * Helper function to ease the writing of AbstractType.toString() methods. */
public static String stringifyAliasesParameters(Map<Byte, AbstractType<?>> aliases) { StringBuilder sb = new StringBuilder(); sb.append('('); Iterator<Map.Entry<Byte, AbstractType<?>>> iter = aliases.entrySet().iterator(); if (iter.hasNext()) { Map.Entry<Byte, AbstractType<?>> entry = iter.next(); sb.append((char)(byte)entry.getKey()).append("=>").append(entry.getValue()); } while (iter.hasNext()) { Map.Entry<Byte, AbstractType<?>> entry = iter.next(); sb.append(',').append((char)(byte)entry.getKey()).append("=>").append(entry.getValue()); } sb.append(')'); return sb.toString(); }
Helper function to ease the writing of AbstractType.toString() methods.
/** * Helper function to ease the writing of AbstractType.toString() methods. */
public static String stringifyTypeParameters(List<AbstractType<?>> types) { return stringifyTypeParameters(types, false); }
Helper function to ease the writing of AbstractType.toString() methods.
/** * Helper function to ease the writing of AbstractType.toString() methods. */
public static String stringifyTypeParameters(List<AbstractType<?>> types, boolean ignoreFreezing) { StringBuilder sb = new StringBuilder("("); for (int i = 0; i < types.size(); i++) { if (i > 0) sb.append(","); sb.append(types.get(i).toString(ignoreFreezing)); } return sb.append(')').toString(); } public static String stringifyCollectionsParameters(Map<ByteBuffer, ? extends CollectionType> collections) { StringBuilder sb = new StringBuilder(); sb.append('('); boolean first = true; for (Map.Entry<ByteBuffer, ? extends CollectionType> entry : collections.entrySet()) { if (!first) sb.append(','); first = false; sb.append(ByteBufferUtil.bytesToHex(entry.getKey())).append(":"); sb.append(entry.getValue()); } sb.append(')'); return sb.toString(); } public static String stringifyUserTypeParameters(String keysace, ByteBuffer typeName, List<FieldIdentifier> fields, List<AbstractType<?>> columnTypes, boolean ignoreFreezing) { StringBuilder sb = new StringBuilder(); sb.append('(').append(keysace).append(",").append(ByteBufferUtil.bytesToHex(typeName)); for (int i = 0; i < fields.size(); i++) { sb.append(','); sb.append(ByteBufferUtil.bytesToHex(fields.get(i).bytes)).append(":"); sb.append(columnTypes.get(i).toString(ignoreFreezing)); } sb.append(')'); return sb.toString(); } }