From 8c869395d0bd3eb7b1e1db31a7bdd4ade59e625c Mon Sep 17 00:00:00 2001 From: Jeremy Chapman Date: Thu, 14 Jun 2018 14:46:36 -0700 Subject: [PATCH] Reflection-based ShadedTypeParser to minimize copy-paste (previous was more copy-paste with no reflection) --- .../db/marshal/ShadedTypeParser.java | 490 +++++++----------- 1 file changed, 180 insertions(+), 310 deletions(-) diff --git a/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java b/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java index c41c7c1aa..9c73a4acc 100644 --- a/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java +++ b/astyanax-cassandra/src/main/java/com/netflix/astyanax/shaded/org/apache/cassandra/db/marshal/ShadedTypeParser.java @@ -17,271 +17,218 @@ import com.netflix.astyanax.shaded.org.apache.cassandra.exceptions.ConfigurationException; import com.netflix.astyanax.shaded.org.apache.cassandra.exceptions.SyntaxException; -import com.netflix.astyanax.shaded.org.apache.cassandra.utils.ByteBufferUtil; import com.netflix.astyanax.shaded.org.apache.cassandra.utils.FBUtilities; +import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.util.*; +import java.util.Map; /** - * Extend TypeParser to support shaded {@link AbstractType} from shaded package. - * Converts input class names to shaded package name. + * Extend {@link TypeParser} to support shaded {@link AbstractType} from shaded package. * - * Note that shading prefixes this class's package name with the shaded name {@link #SHADED_PREFIX}. - * - * Effectively uses {@link TypeParser} only as an interface. The implementation is a slightly altered copy-paste - * of {@link TypeParser} because {@link TypeParser#parse(String)} can't really be overridden. That method is public but - * references private fields (e.g. {@link TypeParser#idx}, etc.) and private methods - * ({@link TypeParser#getAbstractType}, etc.) which a derived class can't access. - * - * This is a very ugly way to support shading cassandra-all, but it's still the least invasive approach. + * This implementation uses some ugly reflection because {@link TypeParser#parse(String)} was apparently never meant + * to be overridden -- too many references to private fields (e.g. {@link TypeParser#idx}, etc.) and private methods + * ({@link TypeParser#getAbstractType}, etc.) which a derived method can't access. This ugliness could have been + * avoided if TypeParser hadn't tried to restrict extension by setting everything private instead of protected. */ public class ShadedTypeParser extends TypeParser { - public static final String SHADED_PREFIX = "com.netflix.astyanax.shaded."; - protected final String str; - protected int idx; - protected static final Map> cache = new HashMap(); - protected static final TypeParser EMPTY_PARSER = new ShadedTypeParser(""); + private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(ShadedTypeParser.class); - public static ShadedTypeParser buildTypeParser(String str, int idx){ - return new ShadedTypeParser(str, idx); - } + public static final String SHADED_PREFIX = "com.netflix.astyanax.shaded."; - public ShadedTypeParser(String str) { - // TypeParser's private fields aren't actually used, but we're still required to invoke the super constructor - super(str); - this.str = str; - this.idx = 0; - } + protected static TypeParser EMPTY_PARSER; - public ShadedTypeParser(String str, int idx) { - // TypeParser's private fields aren't actually used, but we're still required to invoke the super constructor - super(str); - this.str = str; - this.idx = idx; - } - - public static String getShadedClassName(String className){ - if(className.startsWith(SHADED_PREFIX)){ - return className; - } else if (className.contains(".")){ - return SHADED_PREFIX + className; - } else { - return SHADED_PREFIX + "org.apache.cassandra.db.marshal." + className; + static { + try { + EMPTY_PARSER = new ShadedTypeParser(""); + } catch (ConfigurationException e) { + Logger.error("ShadedTypeParser failed to create EMPTY_PARSER; message=" + e.getMessage(), e); } } - public static String getShadedTypeName(String typeName){ - if ( typeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { - return typeName.substring( "org.apache.cassandra.db.marshal.".length() ); - } else if ( typeName.startsWith( SHADED_PREFIX + "org.apache.cassandra.db.marshal." ) ) { - return typeName.substring( (SHADED_PREFIX + "org.apache.cassandra.db.marshal.").length() ); - } - return typeName; - } + /******************************************************************************************************************* + * Begin ugly reflection required to work around TypeParser's lack of design for extension: + ******************************************************************************************************************/ + protected static Field strField; + protected static Field idxField; + protected static Field cacheField; + protected static Map> cache; + protected static Method skipBlankMethod; + protected static Method skipBlankMethod2; + protected static Method isEOSMethod; + protected static Method isEOSMethod2; + protected static Method isIdentifierCharMethod; + + protected static void init() throws ConfigurationException { + try { + strField = ShadedTypeParser.class.getSuperclass().getDeclaredField("str"); + strField.setAccessible(true); - public static AbstractType parse(String str) throws SyntaxException, ConfigurationException { - if (str == null) { - return BytesType.instance; - } else { - str = getShadedClassName(str); - AbstractType type = (AbstractType)cache.get(str); - if (type != null) { - return type; - } else { - int i = 0; - i = skipBlank(str, i); + idxField = ShadedTypeParser.class.getSuperclass().getDeclaredField("idx"); + idxField.setAccessible(true); - int j; - for(j = i; !isEOS(str, i) && isIdentifierChar(str.charAt(i)); ++i) { - ; - } + cacheField = ShadedTypeParser.class.getSuperclass().getDeclaredField("cache"); + cacheField.setAccessible(true); + cache = (Map>)cacheField.get(null); - if (i == j) { - return BytesType.instance; - } else { - String name = str.substring(j, i); - name = getShadedClassName(name); - i = skipBlank(str, i); - if (!isEOS(str, i) && str.charAt(i) == '(') { - ShadedTypeParser typeParser = buildTypeParser(str, i); - type = getAbstractType(name, typeParser); - } else { - type = getAbstractType(name); - } + skipBlankMethod = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("skipBlank"); + skipBlankMethod.setAccessible(true); - cache.put(str, type); - return type; - } - } - } - } + skipBlankMethod2 = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("skipBlank", String.class, int.class); + skipBlankMethod2.setAccessible(true); - public AbstractType parse() throws SyntaxException, ConfigurationException { - this.skipBlank(); - String name = this.readNextIdentifier(); - name = getShadedClassName(name); - this.skipBlank(); - return !this.isEOS() && this.str.charAt(this.idx) == '(' ? getAbstractType(name, this) : getAbstractType(name); - } + isEOSMethod = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("isEOS"); + isEOSMethod.setAccessible(true); - public Map getKeyValueParameters() throws SyntaxException { - Map map = new HashMap(); - if (this.isEOS()) { - return map; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); - } else { - ++this.idx; - - String k; - String v; - for(; this.skipBlankAndComma(); map.put(k, v)) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return map; - } + isEOSMethod2 = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("isEOS", String.class, int.class); + isEOSMethod2.setAccessible(true); - k = this.readNextIdentifier(); - v = ""; - this.skipBlank(); - if (this.str.charAt(this.idx) == '=') { - ++this.idx; - this.skipBlank(); - v = this.readNextIdentifier(); - } else if (this.str.charAt(this.idx) != ',' && this.str.charAt(this.idx) != ')') { - this.throwSyntaxError("unexpected character '" + this.str.charAt(this.idx) + "'"); - } - } + isIdentifierCharMethod = ShadedTypeParser.class.getSuperclass().getDeclaredMethod("isIdentifierChar", int.class); + isIdentifierCharMethod.setAccessible(true); - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + throw new ConfigurationException( + "ShadedTypeParser init() failed for reflection; message=" + e.getMessage(), e); } } - public List> getTypeParameters() throws SyntaxException, ConfigurationException { - List> list = new ArrayList(); - if (this.isEOS()) { - return list; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); - } else { - ++this.idx; + protected String getStr() throws IllegalAccessException { + return (String)strField.get(this); + } - while(this.skipBlankAndComma()) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return list; - } + protected int getIdx() throws IllegalAccessException { + return idxField.getInt(this); + } - try { - list.add(this.parse()); - } catch (SyntaxException var4) { - SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); - ex.initCause(var4); - throw ex; - } - } + protected void setIdx(int idx) throws IllegalAccessException { + idxField.setInt(this, idx); + } - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); - } + protected void skipBlank() throws InvocationTargetException, IllegalAccessException { + skipBlankMethod.invoke(this); } - public Map> getAliasParameters() throws SyntaxException, ConfigurationException { - Map> map = new HashMap(); - if (this.isEOS()) { - return map; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); - } else { - ++this.idx; + protected static int skipBlank(String str, int i) throws InvocationTargetException, IllegalAccessException { + return (Integer)skipBlankMethod2.invoke(null, str, i); + } - while(this.skipBlankAndComma()) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return map; - } + protected boolean isEOS() throws InvocationTargetException, IllegalAccessException { + return (Boolean)isEOSMethod.invoke(this); + } - String alias = this.readNextIdentifier(); - if (alias.length() != 1) { - this.throwSyntaxError("An alias should be a single character"); - } + protected static boolean isEOS(String str, int i) throws InvocationTargetException, IllegalAccessException { + return (Boolean)isEOSMethod2.invoke(null, str, i); + } - char aliasChar = alias.charAt(0); - if (aliasChar < '!' || aliasChar > 127) { - this.throwSyntaxError("An alias should be a single character in [0..9a..bA..B-+._&]"); - } + private static boolean isIdentifierChar(int c) throws InvocationTargetException, IllegalAccessException { + return (Boolean)isIdentifierCharMethod.invoke(null, c); + } - this.skipBlank(); - if (this.str.charAt(this.idx) != '=' || this.str.charAt(this.idx + 1) != '>') { - this.throwSyntaxError("expecting '=>' token"); - } + protected static Method getRawAbstractTypeMethod(Class typeClass) throws NoSuchMethodException { + return ShadedTypeParser.class.getSuperclass().getDeclaredMethod("getRawAbstractType", + typeClass, EMPTY_PARSER.getClass()); + } - this.idx += 2; - this.skipBlank(); + /******************************************************************************************************************* + * End ugly reflection required to work around TypeParser's lack of design for extension + ******************************************************************************************************************/ - try { - map.put((byte)aliasChar, this.parse()); - } catch (SyntaxException var6) { - SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); - ex.initCause(var6); - throw ex; - } - } + public static ShadedTypeParser buildTypeParser(String str, int idx) throws ConfigurationException { + return new ShadedTypeParser(str, idx); + } + + public ShadedTypeParser(String str) throws ConfigurationException { + this(str, 0); + } - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + public ShadedTypeParser(String str, int idx) throws ConfigurationException { + super(str); + init(); + try { + setIdx(idx); + } catch (IllegalAccessException e) { + throw new ConfigurationException( + "ShadedTypeParser constructor failed for reflection; message=" + e.getMessage(), e); } } - public Map getCollectionsParameters() throws SyntaxException, ConfigurationException { - Map map = new HashMap(); - if (this.isEOS()) { - return map; - } else if (this.str.charAt(this.idx) != '(') { - throw new IllegalStateException(); + public static String getShadedClassName(String className){ + if(className.startsWith(SHADED_PREFIX)){ + return className; + } else if (className.contains(".")){ + return SHADED_PREFIX + className; } else { - ++this.idx; - - while(this.skipBlankAndComma()) { - if (this.str.charAt(this.idx) == ')') { - ++this.idx; - return map; - } - - String bbHex = this.readNextIdentifier(); - ByteBuffer bb = null; - - try { - bb = ByteBufferUtil.hexToBytes(bbHex); - } catch (NumberFormatException var7) { - this.throwSyntaxError(var7.getMessage()); - } + return SHADED_PREFIX + "org.apache.cassandra.db.marshal." + className; + } + } - this.skipBlank(); - if (this.str.charAt(this.idx) != ':') { - this.throwSyntaxError("expecting ':' token"); - } + public static String getShadedTypeName(String typeName){ + if ( typeName.startsWith( "org.apache.cassandra.db.marshal." ) ) { + return typeName.substring( "org.apache.cassandra.db.marshal.".length() ); + } else if ( typeName.startsWith( SHADED_PREFIX + "org.apache.cassandra.db.marshal." ) ) { + return typeName.substring( (SHADED_PREFIX + "org.apache.cassandra.db.marshal.").length() ); + } + return typeName; + } - ++this.idx; - this.skipBlank(); + /******************************************************************************************************************* + * Begin methods very slightly modified from {@link TypeParser} to support shaded classes + ******************************************************************************************************************/ + public static AbstractType parse(String str) throws SyntaxException, ConfigurationException { + try{ + if (str == null) { + return BytesType.instance; + } else { + str = getShadedClassName(str); + AbstractType type = null; + type = (AbstractType) cache.get(str); + if (type != null) { + return type; + } else { + int i = 0; + i = skipBlank(str, i); - try { - AbstractType type = this.parse(); - if (!(type instanceof CollectionType)) { - throw new SyntaxException(type.toString() + " is not a collection type"); + int j; + for (j = i; !isEOS(str, i) && isIdentifierChar(str.charAt(i)); ++i) { + ; } - map.put(bb, (CollectionType)type); - } catch (SyntaxException var6) { - SyntaxException ex = new SyntaxException(String.format("Exception while parsing '%s' around char %d", this.str, this.idx)); - ex.initCause(var6); - throw ex; + if (i == j) { + return BytesType.instance; + } else { + String name = str.substring(j, i); + name = getShadedClassName(name); + i = skipBlank(str, i); + if (!isEOS(str, i) && str.charAt(i) == '(') { + ShadedTypeParser typeParser = buildTypeParser(str, i); + type = getAbstractType(name, typeParser); + } else { + type = getAbstractType(name); + } + + cache.put(str, type); + return type; + } } } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new ConfigurationException( + "ShadedTypeParser parse(String) failed for reflection; message=" + e.getMessage(), e); + } + } - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: unexpected end of string", this.str, this.idx)); + public AbstractType parse() throws SyntaxException, ConfigurationException { + try { + skipBlank(); + String name = this.readNextIdentifier(); + name = getShadedClassName(name); + skipBlank(); + return !isEOS() && this.getStr().charAt(getIdx()) == '(' ? getAbstractType(name, this) : getAbstractType(name); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new ConfigurationException( + "ShadedTypeParser parse() failed for reflection; message=" + e.getMessage(), e); } } @@ -292,10 +239,14 @@ protected static AbstractType getAbstractType(String compareWith) throws Conf try { Field field = typeClass.getDeclaredField("instance"); return (AbstractType)field.get((Object)null); - } catch (NoSuchFieldException var4) { - return getRawAbstractType(typeClass, EMPTY_PARSER); - } catch (IllegalAccessException var5) { - return getRawAbstractType(typeClass, EMPTY_PARSER); + } catch (NoSuchFieldException | IllegalAccessException var4) { + try { + Method getRawAbstractTypeMethod = getRawAbstractTypeMethod(typeClass); + return (AbstractType) getRawAbstractTypeMethod.invoke(null, typeClass, EMPTY_PARSER); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new ConfigurationException( + "ShadedTypeParser getAbstractType failed for reflection; message=" + e.getMessage(), e); + } } } @@ -307,11 +258,14 @@ protected static AbstractType getAbstractType(String compareWith, TypeParser try { Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); return (AbstractType)method.invoke((Object)null, parser); - } catch (NoSuchMethodException var6) { - type = getRawAbstractType(typeClass); - return AbstractType.parseDefaultParameters(type, parser); - } catch (IllegalAccessException var7) { - type = getRawAbstractType(typeClass); + } catch (NoSuchMethodException | IllegalAccessException var6) { + try { + Method getRawAbstractTypeMethod = getRawAbstractTypeMethod(typeClass); + type = (AbstractType) getRawAbstractTypeMethod.invoke(null, typeClass); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new ConfigurationException( + "ShadedTypeParser getAbstractType() failed for reflection; message=" + e.getMessage(), e); + } return AbstractType.parseDefaultParameters(type, parser); } catch (InvocationTargetException var8) { ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); @@ -320,92 +274,8 @@ protected static AbstractType getAbstractType(String compareWith, TypeParser } } - protected static AbstractType getRawAbstractType(Class> typeClass) throws ConfigurationException { - try { - Field field = typeClass.getDeclaredField("instance"); - return (AbstractType)field.get((Object)null); - } catch (NoSuchFieldException var2) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } catch (IllegalAccessException var3) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } - } - - protected static AbstractType getRawAbstractType(Class> typeClass, TypeParser parser) throws ConfigurationException { - try { - Method method = typeClass.getDeclaredMethod("getInstance", TypeParser.class); - return (AbstractType)method.invoke((Object)null, parser); - } catch (NoSuchMethodException var4) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } catch (IllegalAccessException var5) { - throw new ConfigurationException("Invalid comparator class " + typeClass.getName() + ": must define a public static instance field or a public static method getInstance(TypeParser)."); - } catch (InvocationTargetException var6) { - ConfigurationException ex = new ConfigurationException("Invalid definition for comparator " + typeClass.getName() + "."); - ex.initCause(var6.getTargetException()); - throw ex; - } - } - - protected void throwSyntaxError(String msg) throws SyntaxException { - throw new SyntaxException(String.format("Syntax error parsing '%s' at char %d: %s", this.str, this.idx, msg)); - } - - protected boolean isEOS() { - return isEOS(this.str, this.idx); - } - - protected static boolean isEOS(String str, int i) { - return i >= str.length(); - } - - protected static boolean isBlank(int c) { - return c == 32 || c == 9 || c == 10; - } - - protected void skipBlank() { - this.idx = skipBlank(this.str, this.idx); - } + /******************************************************************************************************************* + * End methods copy-pasted from {@link TypeParser} and modified slightly to to work with shaded classes + ******************************************************************************************************************/ - protected static int skipBlank(String str, int i) { - while(!isEOS(str, i) && isBlank(str.charAt(i))) { - ++i; - } - - return i; - } - - protected boolean skipBlankAndComma() { - for(boolean commaFound = false; !this.isEOS(); ++this.idx) { - int c = this.str.charAt(this.idx); - if (c == ',') { - if (commaFound) { - return true; - } - - commaFound = true; - } else if (!isBlank(c)) { - return true; - } - } - - return false; - } - - protected static boolean isIdentifierChar(int c) { - return c >= 48 && c <= 57 || c >= 97 && c <= 122 || c >= 65 && c <= 90 || c == 45 || c == 43 || c == 46 || c == 95 || c == 38; - } - - public String readNextIdentifier() { - int i; - for(i = this.idx; !this.isEOS() && isIdentifierChar(this.str.charAt(this.idx)); ++this.idx) { - ; - } - - return this.str.substring(i, this.idx); - } - - public char readNextChar() { - this.skipBlank(); - return this.str.charAt(this.idx++); - } }