/* * Copyright 2013 the original author or authors. * * 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 org.springframework.xd.dirt.stream.completion; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.xd.dirt.stream.ParsingContext; import org.springframework.xd.dirt.stream.XDParser; import org.springframework.xd.module.ModuleDescriptor; import org.springframework.xd.module.options.ModuleOption; import org.springframework.xd.rest.domain.CompletionKind; /** * Provides code completion on a (maybe ill-formed) stream definition. * * @author Eric Bottard */ @Service // Lazy needed to prevent circular dependency with ExpandOneDashToTwoDashesRecoveryStrategy @Lazy public class CompletionProvider { /** * The number of times (inclusive) the user must invoke completion for {@link ModuleOption#hidden(boolean) hidden} * options to show up */ private final static int HIDDEN_OPTION_THRESHOLD = 2; private final XDParser parser; private final List<CompletionRecoveryStrategy<Exception>> completionRecoveryStrategies; private final List<CompletionExpansionStrategy> completionExpansionStrategies; public static boolean shouldShowOption(ModuleOption option, int detailLevel) { return !option.isHidden() || detailLevel >= HIDDEN_OPTION_THRESHOLD; } /** * Construct a new CompletionProvider given a list of recovery strategies and expansion strategies. * * @param parser the parser used to parse the text the partial module definition. * @param completionRecoveryStrategies list of strategies to apply when an exception was thrown during parsing. * @param completionExpansionStrategies list of strategies to apply for adding additional module options completion * suggestions. */ @Autowired @SuppressWarnings("unchecked") public CompletionProvider(XDParser parser, List<CompletionRecoveryStrategy<? extends Exception>> completionRecoveryStrategies, List<CompletionExpansionStrategy> completionExpansionStrategies) { this.parser = parser; // Unchecked downcast here is the best compromise // if we want to still benefit from Spring's typed collection injection Object o = completionRecoveryStrategies; this.completionRecoveryStrategies = (List<CompletionRecoveryStrategy<Exception>>) o; this.completionExpansionStrategies = completionExpansionStrategies; } /* * Attempt to parse the text the user has already typed in. This either succeeds, in which case we may propose to * expand what she has typed, or it fails (most likely because this is not well formed), in which case we try to * recover from the parsing failure and still add proposals. */ public List<String> complete(CompletionKind kind, String start, int detailLevel) { List<String> results = new ArrayList<String>(); String name = "__dummy"; List<ModuleDescriptor> parsed = null; try { parsed = parser.parse(name, start, toParsingContext(kind)); } catch (Exception recoverable) { for (CompletionRecoveryStrategy<Exception> strategy : completionRecoveryStrategies) { if (strategy.shouldTrigger(start, recoverable, kind)) { strategy.addProposals(start, recoverable, kind, detailLevel, results); } } return results; } for (CompletionExpansionStrategy strategy : completionExpansionStrategies) { if (strategy.shouldTrigger(start, parsed, kind)) { strategy.addProposals(start, parsed, kind, detailLevel, results); } } return results; } static ParsingContext toParsingContext(CompletionKind kind) { switch (kind) { case stream: return ParsingContext.partial_stream; case module: return ParsingContext.partial_module; case job: return ParsingContext.partial_job; default: throw new IllegalArgumentException("Unknown kind: " + kind); } } }