Skip to content

Instantly share code, notes, and snippets.

@fge
Created March 28, 2015 13:05
Show Gist options
  • Save fge/ef0c8457ab0947c71019 to your computer and use it in GitHub Desktop.
Save fge/ef0c8457ab0947c71019 to your computer and use it in GitHub Desktop.
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