/* * Copyright 2003-2017 JetBrains s.r.o. * * Licensed 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 jetbrains.mps.generator.impl.interpreted; import jetbrains.mps.generator.impl.GenerationFailureException; import jetbrains.mps.generator.impl.TemplateQueryException; import jetbrains.mps.generator.impl.query.CallArgumentQuery; import jetbrains.mps.generator.impl.query.CreateRootCondition; import jetbrains.mps.generator.impl.query.DropAttributeRuleCondition; import jetbrains.mps.generator.impl.query.DropRuleCondition; import jetbrains.mps.generator.impl.query.IfMacroCondition; import jetbrains.mps.generator.impl.query.InlineSwitchCaseCondition; import jetbrains.mps.generator.impl.query.InsertMacroQuery; import jetbrains.mps.generator.impl.query.MapConfigurationCondition; import jetbrains.mps.generator.impl.query.MapNodeQuery; import jetbrains.mps.generator.impl.query.MapPostProcessor; import jetbrains.mps.generator.impl.query.MapRootRuleCondition; import jetbrains.mps.generator.impl.query.PatternRuleQuery; import jetbrains.mps.generator.impl.query.PropertyValueQuery; import jetbrains.mps.generator.impl.query.QueryKey; import jetbrains.mps.generator.impl.query.QueryKeyImpl; import jetbrains.mps.generator.impl.query.QueryProviderBase; import jetbrains.mps.generator.impl.query.ReductionRuleCondition; import jetbrains.mps.generator.impl.query.ReferenceTargetQuery; import jetbrains.mps.generator.impl.query.ScriptCodeBlock; import jetbrains.mps.generator.impl.query.SourceNodeQuery; import jetbrains.mps.generator.impl.query.SourceNodesQuery; import jetbrains.mps.generator.impl.query.VariableValueQuery; import jetbrains.mps.generator.impl.query.WeaveAnchorQuery; import jetbrains.mps.generator.impl.query.WeaveRuleCondition; import jetbrains.mps.generator.impl.query.WeaveRuleQuery; import jetbrains.mps.generator.template.CreateRootRuleContext; import jetbrains.mps.generator.template.DropAttributeRuleContext; import jetbrains.mps.generator.template.DropRootRuleContext; import jetbrains.mps.generator.template.IfMacroContext; import jetbrains.mps.generator.template.InlineSwitchCaseContext; import jetbrains.mps.generator.template.InsertMacroContext; import jetbrains.mps.generator.template.MapRootRuleContext; import jetbrains.mps.generator.template.MapSrcMacroContext; import jetbrains.mps.generator.template.MapSrcMacroPostProcContext; import jetbrains.mps.generator.template.MappingScriptContext; import jetbrains.mps.generator.template.PatternRuleContext; import jetbrains.mps.generator.template.PropertyMacroContext; import jetbrains.mps.generator.template.ReductionRuleQueryContext; import jetbrains.mps.generator.template.ReferenceMacroContext; import jetbrains.mps.generator.template.SourceSubstituteMacroNodeContext; import jetbrains.mps.generator.template.SourceSubstituteMacroNodesContext; import jetbrains.mps.generator.template.TemplateArgumentContext; import jetbrains.mps.generator.template.TemplateFunctionMethodName; import jetbrains.mps.generator.template.TemplateQueryContext; import jetbrains.mps.generator.template.TemplateVarContext; import jetbrains.mps.generator.template.WeavingAnchorContext; import jetbrains.mps.generator.template.WeavingMappingRuleContext; import jetbrains.mps.lang.pattern.GeneratedMatchingPattern; import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations; import jetbrains.mps.util.IterableUtil; import jetbrains.mps.util.QueryMethods; import jetbrains.mps.util.QueryMethods.IllegalQueryMethodException; import jetbrains.mps.util.QueryMethods.QueryMethod; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SProperty; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeAccessUtil; import org.jetbrains.mps.openapi.model.SNodeReference; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Collections; /** * Access to QueriesGenerated methods via reflection * * @author Artem Tikhomirov */ public class ReflectiveQueryProvider extends QueryProviderBase { private final QueryMethods myQueryMethods; public ReflectiveQueryProvider(Class<?> generatedQueries) { super(1); myQueryMethods = new QueryMethods(generatedQueries); } @NotNull @Override public CreateRootCondition getCreateRootRuleCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.createRootRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod); } @NotNull @Override public MapRootRuleCondition getMapRootRuleCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.baseMappingRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod); } @NotNull @Override public ReductionRuleCondition getReductionRuleCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.baseMappingRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod); } @NotNull @Override public PatternRuleQuery getPatternRuleCondition(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.patternRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<>(myQueryMethods, methodName, null); } @NotNull @Override public DropRuleCondition getDropRuleCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.dropRootRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod, true); } @NotNull @Override public DropAttributeRuleCondition getDropAttributeRuleCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.dropAttributeRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod, true); } @NotNull @Override public WeaveRuleCondition getWeaveRuleCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.baseMappingRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod); } @NotNull @Override public WeaveRuleQuery getWeaveRuleQuery(@NotNull QueryKey identity) { String contentNodeMethod = TemplateFunctionMethodName.weaving_MappingRule_ContextNodeQuery(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<>(myQueryMethods, contentNodeMethod, null); } @NotNull @Override public WeaveAnchorQuery getWeaveAnchorQuery(@NotNull QueryKey identity) { String anchorQueryMethod = TemplateFunctionMethodName.weaving_AnchorQuery(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<>(myQueryMethods, anchorQueryMethod, null); } @NotNull @Override public ScriptCodeBlock getScriptCodeBlock(@NotNull QueryKey identity) { String codeBlockMethod = TemplateFunctionMethodName.mappingScript_CodeBlock(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<>(myQueryMethods, codeBlockMethod, null /*script doesn't return anything, this is just to tell we need default no-op behavior*/); } @NotNull @Override public MapConfigurationCondition getMapConfigurationCondition(@NotNull QueryKey identity) { String conditionMethod = TemplateFunctionMethodName.mappingConfiguration_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, conditionMethod, true); } @NotNull @Override public SourceNodeQuery getSourceNodeQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.sourceSubstituteMacro_SourceNodeQuery(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<SNode>(myQueryMethods, methodName, null); } @NotNull @Override public SourceNodesQuery getSourceNodesQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.sourceSubstituteMacro_SourceNodesQuery(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<Iterable<SNode>>(myQueryMethods, methodName, Collections.emptyList()); } @NotNull @Override public PropertyValueQuery getPropertyValueQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.propertyMacro_GetPropertyValue(((QueryKeyImpl) identity).getQueryNodeId()); // FIXME this is wrong use of getAPITransitionNode(). The problem here is that PVQ, unlike other queries, is expected to tell // SProperty + default template value. Generated code could have these injected, interpreted needs to calculate these // and can easily do so in TemplateNode, however both need to hide behind QueryKey api which doesn't suggest means to pass extra values // Shall re-design this and decide whether I need Query to be more than just executable code (SProperty+default value look reasonable to // get generated right away along with the query!) or it complicates matters way too much. SNode propertyMacro = ((QueryKeyImpl) identity).getAPITransitionNode(); SNode templateNode = propertyMacro.getParent(); final SProperty property = AttributeOperations.getProperty(propertyMacro); final Object templateValue = SNodeAccessUtil.getProperty(templateNode, property); return new PropMacro(myQueryMethods, propertyMacro.getReference(), methodName, property, templateValue); } @NotNull @Override public IfMacroCondition getIfMacroCondition(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.ifMacro_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, methodName, false); } @NotNull @Override public InlineSwitchCaseCondition getInlineSwitchCaseCondition(@NotNull QueryKey identity) { final String methodName = TemplateFunctionMethodName.baseMappingRule_Condition(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl(myQueryMethods, methodName, false); } @NotNull @Override public ReferenceTargetQuery getReferenceTargetQuery(@NotNull QueryKey identity) { // XXX in fact, RQP may keep template model and find actual references based on identity's information // so that ReferenceTargetQuery can tell link + defaultResolveInfo (like PropertyValueQuery does) String methodName = TemplateFunctionMethodName.referenceMacro_GetReferent(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<>(myQueryMethods, methodName, null); } @NotNull @Override public CallArgumentQuery getTemplateCallArgumentQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.templateArgumentQuery(((QueryKeyImpl) identity).getQueryNodeId()); // DefaultQueryExecutionContext used to evaluate to null if no method was found. // We need that to support bootstrap for generator templates (e.g. if I add an argument to a CALL inside QueriesGenerated template) return new Impl2<>(myQueryMethods, methodName, null); } @NotNull @Override public VariableValueQuery getVariableValueQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.varValue_Query(((QueryKeyImpl) identity).getQueryNodeId()); // DefaultQueryExecutionContext used to evaluate to null if no method was found. // We need that to support bootstrap for generator templates (e.g. if I add a new VAR into QueriesGenerated template) return new Impl2<>(myQueryMethods, methodName, null); } @NotNull @Override public InsertMacroQuery getInsertMacroQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.insertMacro_Query(((QueryKeyImpl) identity).getQueryNodeId()); // DefaultQueryExecutionContext used to evaluate to null if no method was found. // We need that to support bootstrap for generator templates (e.g. if I add a new INSERT into QueriesGenerated template of any // language with bootstrap dependency to self (e.g. bl.collections)) return new Impl2<>(myQueryMethods, methodName, null); } @NotNull @Override public MapNodeQuery getMapNodeQuery(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.mapSrcMacro_MapperFunction(((QueryKeyImpl) identity).getQueryNodeId()); // it's not expected for mapper function to return null return new Impl2<>(myQueryMethods, methodName); } @NotNull @Override public MapPostProcessor getMapPostProcessor(@NotNull QueryKey identity) { String methodName = TemplateFunctionMethodName.mapSrcMacro_PostMapperFunction(((QueryKeyImpl) identity).getQueryNodeId()); return new Impl2<>(myQueryMethods, methodName); } /*package*/ static GenerationFailureException fromQueryAccessCode(IllegalQueryMethodException ex) { return new GenerationFailureException(ex.getMessage(), ex.getCause()); } /*package*/ static GenerationFailureException fromUserCode(String methodName, TemplateQueryContext ctx, InvocationTargetException ite) { SNodeReference templateLocation = ctx.getTemplateReference(); String modelName = templateLocation == null || templateLocation.getModelReference() == null ? "<unknown>" : templateLocation.getModelReference().getModelName(); String msg = String.format("Generated method %s (template model %s) failed", methodName, modelName); TemplateQueryException ex = new TemplateQueryException(msg, ite.getCause()); ex.setQueryContext(ctx); return ex; } /* * NOTE Methods intentionally have try/catch not refactored into single invokeAndReturnValue to keep stack slim * (2 extra arguments on the stack for each call). */ // Boolean condition methods with default value private static final class Impl implements CreateRootCondition, MapRootRuleCondition, ReductionRuleCondition, DropRuleCondition, WeaveRuleCondition, MapConfigurationCondition, IfMacroCondition, InlineSwitchCaseCondition, DropAttributeRuleCondition { private final QueryMethods myQueryMethods; private final String myMethodName; private final boolean myDefValue; private volatile QueryMethod<Boolean> myMethod; Impl(QueryMethods queryMethods, @NotNull String methodName) { this(queryMethods, methodName, false); } Impl(QueryMethods queryMethods, @NotNull String methodName, boolean defValue) { myQueryMethods = queryMethods; myMethodName = methodName; myDefValue = defValue; } private boolean invokeBoolean(TemplateQueryContext ctx) throws GenerationFailureException { // I don't care to synchronize method evaluation as there's no difference for me which QM instance I use, // provided initialization of any is complete (that's why field is volatile) QueryMethod<Boolean> m = myMethod; if (m == null) { if (myQueryMethods.hasMethod(myMethodName)) { m = myQueryMethods.getMethod(myMethodName); } else { String fm = "cannot find condition method '%s' : evaluate to %s"; ctx.showWarningMessage(null, String.format(fm, myMethodName, String.valueOf(myDefValue).toUpperCase())); m = contextObject -> myDefValue; } myMethod = m; } try { return m.invoke(ctx); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, ctx, e); } } @Override public boolean check(@NotNull CreateRootRuleContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull DropRootRuleContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull DropAttributeRuleContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull MapRootRuleContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull ReductionRuleQueryContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull WeavingMappingRuleContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull TemplateQueryContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull IfMacroContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } @Override public boolean check(@NotNull InlineSwitchCaseContext ctx) throws GenerationFailureException { return invokeBoolean(ctx); } } private static final class PropMacro extends PropertyValueQuery.Base { private final QueryMethods myQueryMethods; private final String myMethodName; private QueryMethod<Object> myMethod; public PropMacro(QueryMethods queryMethods, @NotNull SNodeReference macro, @NotNull String methodName, @NotNull SProperty property, Object templateValue) { super(macro, property, templateValue); myQueryMethods = queryMethods; myMethodName = methodName; } @Nullable @Override public Object evaluate(@NotNull PropertyMacroContext context) throws GenerationFailureException { if (myMethod == null) { myMethod = getMethod(context); } try { return myMethod.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } private QueryMethod<Object> getMethod(PropertyMacroContext context) throws GenerationFailureException { // We used to treat missing method for a property macro as GFE, while check or sourceNode queries // got default implementation, and now I've switched to null default value. if (!myQueryMethods.hasMethod(myMethodName)) { final String m = String.format("cannot find method '%s' for property macro", myMethodName); context.showWarningMessage(null, m); return contextObject -> null; } return myQueryMethods.getMethod(myMethodName); } } // queries that evaluate to <T>. Another difference from Impl is that // methods other than that with Boolean get cached in the field. static final class Impl2<T> implements VariableValueQuery, CallArgumentQuery, InsertMacroQuery, MapNodeQuery, MapPostProcessor, ReferenceTargetQuery, SourceNodeQuery, SourceNodesQuery, PatternRuleQuery, WeaveRuleQuery, ScriptCodeBlock, WeaveAnchorQuery { private final QueryMethods myQueryMethods; // not null private final String myMethodName; // not null private final T myMissingMethodValue; private final boolean myUseDefaultForMissing; private QueryMethod<T> myMethod; // arguments are not null public Impl2(QueryMethods queryMethods, String methodName) { myQueryMethods = queryMethods; myMethodName = methodName; myMissingMethodValue = null; myUseDefaultForMissing = false; } // arguments are not null, except for missingMethodValue public Impl2(QueryMethods queryMethods, String methodName, @Nullable T missingMethodValue) { myQueryMethods = queryMethods; myMethodName = methodName; myMissingMethodValue = missingMethodValue; myUseDefaultForMissing = true; } @Nullable @Override public Object evaluate(@NotNull TemplateArgumentContext context) throws GenerationFailureException { try { return getMethod(context, "cannot find method '%s' for template call argument").invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Nullable @Override public Object evaluate(@NotNull TemplateVarContext context) throws GenerationFailureException { try { return getMethod(context, "cannot find method '%s' for VAR macro").invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Nullable @Override public SNode evaluate(@NotNull InsertMacroContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<SNode> method = (QueryMethod<SNode>) getMethod(context, "cannot find method '%s' for INSERT macro"); try { return method.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Nullable @Override public SNode evaluate(@NotNull MapSrcMacroContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<SNode> method = (QueryMethod<SNode>) getMethod(context, "cannot find method '%s' for MAP-SRC's mapping function"); try { return method.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Override public void invoke(@NotNull MapSrcMacroPostProcContext context) throws GenerationFailureException { try { getMethod(context, "cannot find method '%s' for MAP-SRC's post-processor").invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Nullable @Override public Object evaluate(@NotNull ReferenceMacroContext context) throws GenerationFailureException { try { return getMethod(context, "cannot find method '%s' for reference macro").invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Nullable @Override public SNode evaluate(@NotNull SourceSubstituteMacroNodeContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<SNode> method = (QueryMethod<SNode>) getMethod(context, "cannot find node query '%s' : evaluate to null"); try { return method.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @NotNull @Override public Collection<SNode> evaluate(@NotNull SourceSubstituteMacroNodesContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<Iterable<SNode>> method = (QueryMethod<Iterable<SNode>>) getMethod(context, "cannot find nodes query '%s' : evaluate to empty list"); try { Iterable<SNode> result = method.invoke(context); return IterableUtil.asCollection(result); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Override public GeneratedMatchingPattern pattern(@NotNull PatternRuleContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<GeneratedMatchingPattern> method = (QueryMethod<GeneratedMatchingPattern>) getMethod(context, "cannot find pattern condition method '%s' : not applied"); try { return method.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Override public SNode contextNode(WeavingMappingRuleContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<SNode> method = (QueryMethod<SNode>) getMethod(context, "cannot find context node query '%s' : evaluate to null"); try { return method.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Nullable @Override public SNode anchorNode(WeavingAnchorContext context) throws GenerationFailureException { @SuppressWarnings("unchecked") QueryMethod<SNode> method = (QueryMethod<SNode>) getMethod(context, "cannot find anchor node query '%s' : evaluate to null"); try { return method.invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } @Override public void invoke(MappingScriptContext context) throws GenerationFailureException { try { getMethod(context, "cannot run script '%s' : no generated code found").invoke(context); } catch (IllegalQueryMethodException e) { throw fromQueryAccessCode(e); } catch (InvocationTargetException e) { throw fromUserCode(myMethodName, context, e); } } private QueryMethod<T> getMethod(TemplateQueryContext ctx, String messageFormat) throws GenerationFailureException { // trick with local variable is to bring attention to the fact, that parallel evaluate() may // initialize myMethod twice and that invoke may work with an instance other than getMethod() had returned here. // Since QueryMethod is just a simple wrap around reflective call, I'm not too much concerned at the moment that // myMethod.invoke() might run with an instance different that came from getMethod(). Nor there's too much duplicated // effort when looking up a method (QueryMethodGenerated caches them). We've got extra QueryMethod wrappers and 'other this' // for invoke only. // Still, I don't want to bother with synchronization here, with stateless QM I'm unlikely to face any issues, and // though I don't like the code, I'd rather move forward towards non-reflective queries as the only option than // deal with synch of reflective calls. QueryMethod<T> m = myMethod; if (m == null) { if (!myQueryMethods.hasMethod(myMethodName)) { final String msg = String.format(messageFormat, myMethodName); if (myUseDefaultForMissing) { ctx.showWarningMessage(null, msg); return contextObject -> myMissingMethodValue; } else { ctx.showErrorMessage(null, msg); throw new GenerationFailureException(msg); } } m = myMethod = myQueryMethods.getMethod(myMethodName); } return m; } } }