Created
March 28, 2015 13:05
-
-
Save fge/ef0c8457ab0947c71019 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public final class RuleLookup<P extends SonarParserBase> | |
{ | |
private static final MethodHandles.Lookup LOOKUP | |
= MethodHandles.publicLookup(); | |
private static final MethodType RULE_METHOD | |
= MethodType.methodType(Rule.class); | |
private static final MethodType METHOD_TYPE | |
= MethodType.methodType(Rule.class, SonarParserBase.class); | |
private final Class<P> parserClass; | |
private final P parser; | |
private final Rule mainRule; | |
private final Map<String, Rule> rules = new HashMap<>(); | |
public RuleLookup(final Class<P> parserClass) | |
{ | |
this.parserClass = Objects.requireNonNull(parserClass); | |
parser = Parboiled.createParser(parserClass); | |
final MethodHandle handle = getMainRuleHandle(); | |
mainRule = handle != null ? getRule(handle) : null; | |
} | |
public Rule getMainRule() | |
{ | |
if (mainRule == null) | |
throw new IllegalStateException("parser does not define a " | |
+ "@MainRule"); | |
return mainRule; | |
} | |
public Rule getRuleByName(final String name) | |
{ | |
Objects.requireNonNull(name); | |
Rule rule; | |
synchronized (rules) { | |
rule = rules.get(name); | |
if (rule == null) { | |
rule = findRuleByName(name); | |
rules.put(name, rule); | |
} | |
} | |
return rule; | |
} | |
private MethodHandle getHandleByName(final String ruleName) | |
{ | |
try { | |
final MethodHandle handle | |
= LOOKUP.findVirtual(parserClass, ruleName, RULE_METHOD); | |
return handle.asType(METHOD_TYPE); | |
} catch (NoSuchMethodException | IllegalAccessException e) { | |
throw new GrappaException("unable to grab a MethodHandle for rule " | |
+ ruleName + ", parser class " + parserClass.getName(), e); | |
} | |
} | |
private Rule findRuleByName(final String ruleName) | |
{ | |
final MethodHandle handle = getHandleByName(ruleName); | |
return getRule(handle); | |
} | |
@Nullable | |
private MethodHandle getMainRuleHandle() | |
{ | |
Method candidate = null; | |
for (final Method method: parserClass.getMethods()) { | |
if (method.getAnnotation(MainRule.class) == null) | |
continue; | |
if (candidate != null) | |
throw new IllegalStateException("only one rule may be annotated" | |
+ " with @MainRule"); | |
candidate = method; | |
checkRuleMethod(method); | |
} | |
if (candidate == null) | |
return null; | |
try { | |
return LOOKUP.unreflect(candidate).asType(METHOD_TYPE); | |
} catch (Error | RuntimeException e) { | |
throw e; | |
} catch (Throwable throwable) { | |
throw new GrappaException("unable to obtain a method handle from" | |
+ " main rule " + candidate.getName(), throwable); | |
} | |
} | |
private static void checkRuleMethod(final Method method) | |
{ | |
if ((method.getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) | |
throw new IllegalStateException("a @MainRule must be declared" | |
+ " public"); | |
if (method.getParameterTypes().length != 0) | |
throw new IllegalStateException("a @MainRule must not take" | |
+ "arguments"); | |
if (method.getReturnType() != Rule.class) | |
throw new IllegalStateException("a @MainRule must return a Rule"); | |
} | |
private Rule getRule(final MethodHandle handle) | |
{ | |
try { | |
return (Rule) handle.invokeExact(parser); | |
} catch (Error | RuntimeException e) { | |
throw e; | |
} catch (Throwable throwable) { | |
throw new GrappaException("method handle invocation failed", | |
throwable); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment