diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 2054401f..ef59bda1 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -40,7 +40,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - version: [2022.3.3, 2023.1.3, 2023.2.2] + version: [2022.3.3, 2023.1.3, 2023.2.3] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 diff --git a/build-all.sh b/build-all.sh index d4b88aed..385ccc4c 100755 --- a/build-all.sh +++ b/build-all.sh @@ -1,5 +1,5 @@ #!/bin/bash -for v in "2022.3.3" "2023.1.3" "2023.2.2"; do +for v in "2022.3.3" "2023.1.3" "2023.2.3"; do ./build.sh $v done diff --git a/build.gradle b/build.gradle index 9f9dbd0f..22f0c6f3 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ allprojects { } group = 'com.github.camel-tooling' - version = '1.1.1' + version = '1.1.5' } subprojects { diff --git a/camel-idea-plugin/build.gradle b/camel-idea-plugin/build.gradle index a0d513f1..9a50b59a 100644 --- a/camel-idea-plugin/build.gradle +++ b/camel-idea-plugin/build.gradle @@ -66,9 +66,9 @@ dependencies { // Needed to avoid conflicts with the version of the maven resolver used by IntelliJ implementation "org.apache.maven.resolver:maven-resolver-api:${mavenResolverVersion}" implementation "org.apache.maven.resolver:maven-resolver-impl:${mavenResolverVersion}" - implementation "io.github.classgraph:classgraph:4.8.162" - implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.2" - implementation "io.fabric8:kubernetes-model-apiextensions:6.9.0" + implementation "io.github.classgraph:classgraph:4.8.163" + implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.3" + implementation "io.fabric8:kubernetes-model-apiextensions:6.9.1" testImplementation "org.apache.camel:camel-core:${camelVersion}" testImplementation "org.apache.camel.springboot:camel-spring-boot:${camelVersion}" @@ -76,7 +76,7 @@ dependencies { testImplementation 'org.jboss.shrinkwrap:shrinkwrap-depchain:1.2.6' testImplementation 'org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-depchain:3.2.1' testImplementation 'org.hamcrest:hamcrest-all:1.3' - testImplementation 'org.springframework:spring-context:6.0.12' + testImplementation 'org.springframework:spring-context:6.0.13' testImplementation "org.apache.camel:camel-jsonpath:${camelVersion}" } diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/contributor/CamelYamlFileReferenceContributor.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/contributor/CamelYamlFileReferenceContributor.java index dbc35e55..ed077081 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/contributor/CamelYamlFileReferenceContributor.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/contributor/CamelYamlFileReferenceContributor.java @@ -18,6 +18,9 @@ import com.github.cameltooling.idea.completion.extension.CamelEndpointNameCompletionExtension; import com.github.cameltooling.idea.completion.extension.CamelEndpointSmartCompletionExtension; +import com.github.cameltooling.idea.completion.extension.CamelKameletNameCompletion; +import com.github.cameltooling.idea.completion.extension.CamelKameletOptionNameCompletion; +import com.github.cameltooling.idea.completion.extension.CamelKameletOptionValueCompletion; import com.github.cameltooling.idea.completion.header.CamelHeaderEndpointSource; import com.github.cameltooling.idea.completion.header.CamelYamlHeaderNameCompletion; import com.github.cameltooling.idea.completion.header.CamelYamlHeaderValueCompletion; @@ -26,13 +29,21 @@ import com.github.cameltooling.idea.util.YamlPatternConditions; import com.intellij.codeInsight.completion.CompletionInitializationContext; import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.patterns.PatternCondition; import com.intellij.patterns.PlatformPatterns; +import com.intellij.patterns.PsiElementPattern; import com.intellij.patterns.StandardPatterns; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.TokenType; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.YAMLElementTypes; import org.jetbrains.yaml.YAMLTokenTypes; import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLMapping; +import org.jetbrains.yaml.psi.YAMLSequence; +import org.jetbrains.yaml.psi.YAMLSequenceItem; import static com.intellij.patterns.PlatformPatterns.psiElement; @@ -41,6 +52,40 @@ */ public class CamelYamlFileReferenceContributor extends CamelContributor { + private static final PatternCondition WITH_FIRST_CHILD_IS_PROPERTIES = YamlPatternConditions.withFirstChild( + psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("properties") + ); + private static final PsiElementPattern.Capture IS_KIND_KAMELET = psiElement(YAMLMapping.class) + .withChild( + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("ref") + ) + ) + .withChild( + psiElement(YAMLMapping.class) + + .withChild( + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withPair("kind", "Kamelet") + ) + ) + .withChild( + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("name") + ) + ) + ) + ) + ); + public CamelYamlFileReferenceContributor() { addCompletionExtension(new CamelEndpointNameCompletionExtension()); addCompletionExtension(new CamelEndpointSmartCompletionExtension(false)); @@ -212,5 +257,177 @@ public CamelYamlFileReferenceContributor() { ), new CamelYamlPropertyValueCompletion() ); + // Detect the name of a Kamelet in a Kamelet binding + addCamelKameletNameCompletion("source", true); + addCamelKameletNameCompletion("steps", false); + addCamelKameletNameCompletion("sink", false); + addCamelKameletOptionNameCompletion(); + addCamelKameletOptionValueCompletion(); + } + + private void addCamelKameletNameCompletion(String ancestorKeyName, boolean onlyConsumer) { + extend(CompletionType.BASIC, + psiElement(YAMLTokenTypes.TEXT) + .withSuperParent( + 2, + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("name") + ) + ) + .withParent( + psiElement(YAMLMapping.class) + .withChild( + PlatformPatterns.psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withPair("kind", "Kamelet") + ) + ) + ) + ) + .withSuperParent( + 4, + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("ref") + ) + ) + ) + .withSuperParent( + 6, + StandardPatterns.or( + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText(ancestorKeyName) + ) + ), + psiElement(YAMLSequenceItem.class) + .withSuperParent( + 2, + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText(ancestorKeyName) + ) + ) + ) + ) + + ), + new CamelKameletNameCompletion(onlyConsumer) + ); + } + + private void addCamelKameletOptionNameCompletion() { + extend(CompletionType.BASIC, + psiElement() + .with( + YamlPatternConditions.or( + psiElement(YAMLTokenTypes.TEXT) + .withSuperParent( + 2, + PlatformPatterns.psiElement(YAMLKeyValue.class) + .with( + WITH_FIRST_CHILD_IS_PROPERTIES + ) + ), + psiElement(YAMLTokenTypes.INDENT) + .afterSibling( + PlatformPatterns.psiElement(YAMLKeyValue.class) + .with( + WITH_FIRST_CHILD_IS_PROPERTIES + ) + ), + psiElement(YAMLTokenTypes.TEXT) + .withSuperParent( + 3, + PlatformPatterns.psiElement(YAMLKeyValue.class) + .with( + WITH_FIRST_CHILD_IS_PROPERTIES + ) + ) + ) + ) + .withAncestor( + 5, + StandardPatterns.or( + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .with(YamlPatternConditions.withText("source", "sink")) + ) + ) + .withChild(IS_KIND_KAMELET), + psiElement(YAMLSequenceItem.class) + .withSuperParent( + 2, + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("steps") + ) + ) + ) + .withChild(IS_KIND_KAMELET) + ) + ), + new CamelKameletOptionNameCompletion() + ); + } + + + private void addCamelKameletOptionValueCompletion() { + extend(CompletionType.BASIC, + psiElement() + .withParent( + psiElement().with( + YamlPatternConditions.withElementType( + YAMLElementTypes.SCALAR_PLAIN_VALUE, YAMLElementTypes.SCALAR_QUOTED_STRING + ) + ) + ) + .withAncestor( + 4, + PlatformPatterns.psiElement(YAMLKeyValue.class) + .with( + WITH_FIRST_CHILD_IS_PROPERTIES + ) + ) + .withAncestor( + 6, + StandardPatterns.or( + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .with(YamlPatternConditions.withText("source", "sink")) + ) + ) + .withChild(IS_KIND_KAMELET), + psiElement(YAMLSequenceItem.class) + .withSuperParent( + 2, + psiElement(YAMLKeyValue.class) + .with( + YamlPatternConditions.withFirstChild( + PlatformPatterns.psiElement(YAMLTokenTypes.SCALAR_KEY) + .withText("steps") + ) + ) + ) + .withChild(IS_KIND_KAMELET) + ) + ), + new CamelKameletOptionValueCompletion() + ); } } diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/endpoint/CamelSmartCompletionEndpointOptions.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/endpoint/CamelSmartCompletionEndpointOptions.java index 1b3cd736..bf34dd61 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/endpoint/CamelSmartCompletionEndpointOptions.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/endpoint/CamelSmartCompletionEndpointOptions.java @@ -60,10 +60,97 @@ public static List addSmartCompletionSuggestionsQueryParameters(f final boolean xmlMode, final PsiElement element, final Editor editor) { + return addSmartCompletionSuggestionsQueryParameters( + new SmartCompletionSuggestionsQueryParametersContext.Default(query, xmlMode, element), + component, existing, element, editor + ); + } + + /** + * Context for the smart completion suggestions query parameters + */ + public interface SmartCompletionSuggestionsQueryParametersContext { + String getQueryAtPosition(); + String getConcatQuery(); + String getSuffix(); + boolean isXmlMode(); + boolean isLookupAsURI(); + boolean isConsumerOnly(); + boolean isProducerOnly(); + char getLookupSuffixChar(); + String getLookupSuffix(); + + /** + * Default implementation of the {@link SmartCompletionSuggestionsQueryParametersContext} + */ + class Default implements SmartCompletionSuggestionsQueryParametersContext { + private final String queryAtPosition; + private final String concatQuery; + private final String suffix; + private final boolean xmlMode; + private final boolean consumerOnly; + private final boolean producerOnly; + + public Default(final String[] query, boolean xmlMode, final PsiElement element) { + this.queryAtPosition = query[2]; + this.concatQuery = query[0]; + this.suffix = query[1]; + this.xmlMode = xmlMode; + final CamelIdeaUtils camelIdeaUtils = CamelIdeaUtils.getService(); + this.consumerOnly = camelIdeaUtils.isConsumerEndpoint(element); + this.producerOnly = camelIdeaUtils.isProducerEndpoint(element); + } + + public String getQueryAtPosition() { + return queryAtPosition; + } + + public String getConcatQuery() { + return concatQuery; + } + + public String getSuffix() { + return suffix; + } + + public boolean isXmlMode() { + return xmlMode; + } + + public boolean isLookupAsURI() { + return true; + } + + @Override + public boolean isConsumerOnly() { + return consumerOnly; + } + + @Override + public boolean isProducerOnly() { + return producerOnly; + } + + public char getLookupSuffixChar() { + return '='; + } + + public String getLookupSuffix() { + return "="; + } + } + } + + @NotNull + public static List addSmartCompletionSuggestionsQueryParameters(final SmartCompletionSuggestionsQueryParametersContext context, + final ComponentModel component, + final Map existing, + final PsiElement element, + final Editor editor) { final List answer = new ArrayList<>(); - String queryAtPosition = query[2]; - if (xmlMode) { + String queryAtPosition = context.getQueryAtPosition(); + if (context.isXmlMode()) { queryAtPosition = queryAtPosition.replace("&", "&"); } @@ -79,16 +166,12 @@ public static List addSmartCompletionSuggestionsQueryParameters(f if ("parameter".equals(option.getKind())) { final String name = option.getName(); - final CamelIdeaUtils camelIdeaUtils = CamelIdeaUtils.getService(); - // if we are consumer only, then any option that has producer in the label should be skipped (as its only for producer) - final boolean consumerOnly = camelIdeaUtils.isConsumerEndpoint(element); - if (consumerOnly && option.getLabel() != null && option.getLabel().contains("producer")) { + if (context.isConsumerOnly() && option.getLabel() != null && option.getLabel().contains("producer")) { continue; } // if we are producer only, then any option that has consumer in the label should be skipped (as its only for consumer) - final boolean producerOnly = camelIdeaUtils.isProducerEndpoint(element); - if (producerOnly && option.getLabel() != null && option.getLabel().contains("consumer")) { + if (context.isProducerOnly() && option.getLabel() != null && option.getLabel().contains("consumer")) { continue; } @@ -101,24 +184,27 @@ public static List addSmartCompletionSuggestionsQueryParameters(f // the lookup should prepare for the new option String lookup; - final String concatQuery = query[0]; - if (!concatQuery.contains("?")) { - // none existing options so we need to start with a ? mark - lookup = queryAtPosition + "?" + key; - } else { - if (!queryAtPosition.endsWith("&") && !queryAtPosition.endsWith("?")) { - lookup = queryAtPosition + "&" + key; + if (context.isLookupAsURI()) { + final String concatQuery = context.getConcatQuery(); + if (!concatQuery.contains("?")) { + // none existing options so we need to start with a ? mark + lookup = queryAtPosition + "?" + key; } else { - // there is already either an ending ? or & - lookup = queryAtPosition + key; + if (!queryAtPosition.endsWith("&") && !queryAtPosition.endsWith("?")) { + lookup = queryAtPosition + "&" + key; + } else { + // there is already either an ending ? or & + lookup = queryAtPosition + key; + } } - } - if (xmlMode) { - lookup = lookup.replace("&", "&"); + if (context.isXmlMode()) { + lookup = lookup.replace("&", "&"); + } + } else { + lookup = queryAtPosition + key; } LookupElementBuilder builder = LookupElementBuilder.create(new OptionSuggestion(option, lookup)); - final String suffix = query[1]; - builder = addInsertHandler(editor, builder, suffix); + builder = addInsertHandler(editor, builder, context.getLookupSuffixChar(), context.getLookupSuffix(), context.getSuffix()); // only show the option in the UI builder = builder.withPresentableText(name); // we don't want to highlight the advanced options which should be more seldom in use @@ -184,7 +270,7 @@ public static List addSmartCompletionSuggestionsContextPath(Strin return answer; } - private static List addSmartCompletionContextPathSuggestions(final String val, + public static List addSmartCompletionContextPathSuggestions(final String val, final ComponentModel component, final Map existing, final Predicate componentPredicate, @@ -287,10 +373,11 @@ private static String removeUnknownEnum(String val, final PsiElement element) { } /** - * We need special logic to determine when it should insert "=" at the end of the options + * We need special logic to determine when it should insert lookup suffix char at the end of the options */ @NotNull private static LookupElementBuilder addInsertHandler(final Editor editor, final LookupElementBuilder builder, + final char lookupSuffixChar, final String lookupSuffix, final String suffix) { return builder.withInsertHandler((context, item) -> { // enforce using replace select char as we want to replace any existing option @@ -300,12 +387,13 @@ private static LookupElementBuilder addInsertHandler(final Editor editor, final //if it's a property file the PsiElement does not start and end with an quot endSelectOffBy = 1; } - final char text = context - .getDocument() - .getCharsSequence() - .charAt(context.getSelectionEndOffset() - endSelectOffBy); - if (text != '=') { - EditorModificationUtil.insertStringAtCaret(editor, "="); + CharSequence content = context + .getDocument() + .getCharsSequence(); + int index = context.getSelectionEndOffset() - endSelectOffBy; + final char text = index < content.length() ? content.charAt(index) : ' '; + if (text != lookupSuffixChar) { + EditorModificationUtil.insertStringAtCaret(editor, lookupSuffix); } } else if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) { // we still want to keep the suffix because they are other options @@ -315,7 +403,7 @@ private static LookupElementBuilder addInsertHandler(final Editor editor, final // strip out first part of suffix until next option value = value.substring(pos); } - EditorModificationUtil.insertStringAtCaret(editor, "=" + value); + EditorModificationUtil.insertStringAtCaret(editor, lookupSuffix + value); // and move cursor back again final int offset = -1 * value.length(); EditorModificationUtil.moveCaretRelatively(editor, offset); diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletCompletion.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletCompletion.java new file mode 100644 index 00000000..c9733dbd --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletCompletion.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion.extension; + +import java.util.List; + +import com.github.cameltooling.idea.service.CamelCatalogService; +import com.github.cameltooling.idea.service.CamelService; +import com.github.cameltooling.idea.util.IdeaUtils; +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.completion.CompletionUtilCore; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import org.apache.camel.catalog.CamelCatalog; +import org.apache.camel.tooling.model.ComponentModel; +import org.jetbrains.annotations.NotNull; + +/** + * {@code CamelKameletCompletion} is a base class for all Camel Kamelet completion providers. + */ +abstract class CamelKameletCompletion extends CompletionProvider { + + protected final CamelEndpointSmartCompletionExtension.Mode mode = CamelEndpointSmartCompletionExtension.Mode.KAMELET; + + @Override + protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) { + Project project = parameters.getOriginalFile().getProject(); + if (project.getService(CamelService.class).isCamelProject()) { + final CamelCatalog camelCatalog = project.getService(CamelCatalogService.class).get(); + final IdeaUtils ideaUtils = IdeaUtils.getService(); + PsiElement element = parameters.getPosition(); + String val = ideaUtils.extractTextFromElement(element, true, true, true); + if (val == null || val.isEmpty()) { + return; + } + val = val.replace(CompletionUtilCore.DUMMY_IDENTIFIER, "") + .replace(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED, ""); + final ComponentModel componentModel = mode.componentModel( + project, camelCatalog, "kamelet", getURIPrefix(element) + val, isConsumerOnly(project, element) + ); + if (componentModel == null) { + return; + } + final List answer = suggestions(componentModel, project, element, parameters.getEditor(), val); + // are there any results then add them + if (!answer.isEmpty()) { + result.withPrefixMatcher(val).addAllElements(answer); + result.stopHere(); + } + } + } + + /** + * @return the URI prefix to use when loading the component model. + */ + @NotNull + protected abstract String getURIPrefix(PsiElement element); + + /** + * @param project the project to check. + * @param element the element corresponding to the position of the completion. + * @return {@code true} if the component is consumer only, {@code false} otherwise. + */ + protected abstract boolean isConsumerOnly(Project project, PsiElement element); + + /** + * @return the list of suggestions for the given input value. + */ + protected abstract List suggestions(@NotNull ComponentModel componentModel, @NotNull Project project, + @NotNull PsiElement element, @NotNull Editor editor, String val); +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletNameCompletion.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletNameCompletion.java new file mode 100644 index 00000000..a038d298 --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletNameCompletion.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion.extension; + +import java.util.List; +import java.util.Map; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import org.apache.camel.tooling.model.ComponentModel; +import org.jetbrains.annotations.NotNull; + +import static com.github.cameltooling.idea.completion.endpoint.CamelSmartCompletionEndpointOptions.addSmartCompletionContextPathSuggestions; + +/** + * {@code CamelKameletNameCompletion} is responsible for completing the name of a Camel Kamelet. + */ +public class CamelKameletNameCompletion extends CamelKameletCompletion { + + private final boolean consumerOnly; + + public CamelKameletNameCompletion(boolean consumerOnly) { + this.consumerOnly = consumerOnly; + } + + @Override + protected @NotNull String getURIPrefix(PsiElement element) { + return "kamelet:"; + } + + @Override + protected boolean isConsumerOnly(Project project, PsiElement element) { + return consumerOnly; + } + + @Override + protected List suggestions(@NotNull ComponentModel componentModel, @NotNull Project project, + @NotNull PsiElement element, @NotNull Editor editor, String val) { + return addSmartCompletionContextPathSuggestions( + "", componentModel, Map.of(), mode.getContextPathComponentPredicate(), + mode.getContextPathOptionPredicate(), mode.getIconProvider(project) + ); + } +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionCompletion.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionCompletion.java new file mode 100644 index 00000000..1c5ddba3 --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionCompletion.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion.extension; + +import com.github.cameltooling.idea.service.KameletService; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nullable; + +/** + * {@code CamelKameletOptionCompletion} is a base class for all Camel Kamelet option completion providers. + */ +abstract class CamelKameletOptionCompletion extends CamelKameletCompletion { + + @Override + protected boolean isConsumerOnly(Project project, PsiElement element) { + String name = getKameletName(element); + return name != null && project.getService(KameletService.class).isConsumer(name); + } + + /** + * Extracts the name of the Camel Kamelet from the given element. + * + * @param element the element + * @return the name of the Camel Kamelet or {@code null} if the element is not part of the configuration of Camel + * Kamelet + */ + protected abstract @Nullable String getKameletName(PsiElement element); +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionNameCompletion.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionNameCompletion.java new file mode 100644 index 00000000..77fbd2e4 --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionNameCompletion.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion.extension; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.github.cameltooling.idea.completion.endpoint.CamelSmartCompletionEndpointOptions; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.apache.camel.tooling.model.ComponentModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLMapping; +import org.jetbrains.yaml.psi.YAMLValue; + +import static com.github.cameltooling.idea.completion.endpoint.CamelSmartCompletionEndpointOptions.addSmartCompletionSuggestionsQueryParameters; + +/** + * {@code CamelKameletOptionNameCompletion} is responsible for completing the name of a Camel Kamelet option. + */ +public class CamelKameletOptionNameCompletion extends CamelKameletOptionCompletion { + + @Override + protected List suggestions(@NotNull ComponentModel componentModel, @NotNull Project project, + @NotNull PsiElement element, @NotNull Editor editor, String val) { + boolean consumerOnly = isConsumerOnly(project, element); + return addSmartCompletionSuggestionsQueryParameters( + new Context(val, consumerOnly), componentModel, existing(element), element, editor + ); + } + + /** + * @return a map of already configured options if any, an empty map otherwise + */ + private Map existing(PsiElement element) { + YAMLKeyValue keyValue = PsiTreeUtil.getParentOfType(element, YAMLKeyValue.class); + if (keyValue == null || !keyValue.getKeyText().equals("properties")) { + return Map.of(); + } + PsiElement lastChild = keyValue.getLastChild(); + if (lastChild instanceof YAMLMapping mapping) { + return mapping.getKeyValues().stream().collect(Collectors.toMap( + YAMLKeyValue::getKeyText, YAMLKeyValue::getValueText + )); + } + return Map.of(); + } + + protected @Nullable String getKameletName(PsiElement element) { + YAMLKeyValue keyValue = PsiTreeUtil.getParentOfType(element, YAMLKeyValue.class); + if (keyValue == null || !keyValue.getKeyText().equals("properties")) { + return null; + } + YAMLMapping mapping = PsiTreeUtil.getParentOfType(keyValue, YAMLMapping.class); + if (mapping == null) { + return null; + } + YAMLKeyValue ref = mapping.getKeyValueByKey("ref"); + if (ref == null) { + return null; + } + YAMLValue value = ref.getValue(); + if (value instanceof YAMLMapping map) { + return Optional.ofNullable(map.getKeyValueByKey("name")) + .map(YAMLKeyValue::getValueText) + .orElse(null); + } + return null; + } + + @Override + protected @NotNull String getURIPrefix(PsiElement element) { + String name = getKameletName(element); + if (name == null) { + return "kamelet:"; + } + return "kamelet:" + name + "?"; + } + + /** + * {@code Context} is a context for the smart completion of Camel Kamelet option names. + */ + private static class Context implements CamelSmartCompletionEndpointOptions.SmartCompletionSuggestionsQueryParametersContext { + + private final String val; + private final boolean consumerOnly; + + Context(String val, boolean consumerOnly) { + this.val = val; + this.consumerOnly = consumerOnly; + } + + @Override + public String getQueryAtPosition() { + return val; + } + + @Override + public String getConcatQuery() { + return null; + } + + @Override + public String getSuffix() { + return val; + } + + @Override + public boolean isXmlMode() { + return false; + } + + @Override + public boolean isLookupAsURI() { + return false; + } + + @Override + public boolean isConsumerOnly() { + return consumerOnly; + } + + @Override + public boolean isProducerOnly() { + return !consumerOnly; + } + + @Override + public char getLookupSuffixChar() { + return ':'; + } + + @Override + public String getLookupSuffix() { + return ": "; + } + } +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionValueCompletion.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionValueCompletion.java new file mode 100644 index 00000000..702a579a --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/completion/extension/CamelKameletOptionValueCompletion.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion.extension; + +import java.util.List; +import java.util.Optional; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.apache.camel.tooling.model.ComponentModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLMapping; +import org.jetbrains.yaml.psi.YAMLValue; + +import static com.github.cameltooling.idea.completion.endpoint.CamelSmartCompletionEndpointValue.addSmartCompletionForEndpointValue; + +/** + * {@code CamelKameletOptionValueCompletion} is responsible for completing the value of a Camel Kamelet option. + */ +public class CamelKameletOptionValueCompletion extends CamelKameletOptionCompletion { + @Override + protected @NotNull String getURIPrefix(PsiElement element) { + String answer = "kamelet:"; + String name = getKameletName(element); + if (name == null) { + return answer; + } + answer += name + "?"; + String optionName = getOptionName(element); + if (optionName == null) { + return answer; + } + answer += optionName + "="; + return answer; + } + + protected @Nullable String getKameletName(PsiElement element) { + YAMLKeyValue keyValue = PsiTreeUtil.getParentOfType(element, YAMLKeyValue.class); + if (keyValue == null) { + return null; + } else if (!keyValue.getKeyText().equals("properties")) { + keyValue = PsiTreeUtil.getParentOfType(keyValue, YAMLKeyValue.class); + if (keyValue == null || !keyValue.getKeyText().equals("properties")) { + return null; + } + } + + YAMLMapping mapping = PsiTreeUtil.getParentOfType(keyValue, YAMLMapping.class); + if (mapping == null) { + return null; + } + YAMLKeyValue ref = mapping.getKeyValueByKey("ref"); + if (ref == null) { + return null; + } + YAMLValue value = ref.getValue(); + if (value instanceof YAMLMapping map) { + return Optional.ofNullable(map.getKeyValueByKey("name")) + .map(YAMLKeyValue::getValueText) + .orElse(null); + } + return null; + } + + /** + * @return the name of the option or {@code null} if the element is not part of the configuration of Camel + */ + private String getOptionName(PsiElement element) { + YAMLKeyValue keyValue = PsiTreeUtil.getParentOfType(element, YAMLKeyValue.class); + if (keyValue == null) { + return null; + } + return keyValue.getKeyText(); + } + + @Override + protected List suggestions(@NotNull ComponentModel componentModel, @NotNull Project project, + @NotNull PsiElement element, @NotNull Editor editor, String val) { + String name = getOptionName(element); + if (name == null) { + return List.of(); + } + ComponentModel.EndpointOptionModel endpointOption = componentModel.getEndpointOptions().stream().filter( + o -> name.equals(o.getName())) + .findFirst().orElse(null); + if (endpointOption == null) { + return List.of(); + } + return addSmartCompletionForEndpointValue(editor, val, "", endpointOption, element, false); + } +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/gradle/GradleFileWriter.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/gradle/GradleFileWriter.java new file mode 100644 index 00000000..e002e5cf --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/gradle/GradleFileWriter.java @@ -0,0 +1,289 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.gradle; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import com.intellij.openapi.vfs.VirtualFile; +import org.apache.maven.model.Dependency; + +/** + * The supported writers of gradle files. + */ +public enum GradleFileWriter { + /** + * The writer dedicated to gradle files written in Kotlin. + */ + KOTLIN { + @Override + public String getInitScriptFileName() { + return "init.gradle.kts"; + } + + @Override + String getListRepositoriesContent() { + return """ + allprojects { + tasks.register("cameltoolingShowRepos") { + repositories + .map{it as MavenArtifactRepository} + .forEach{ println("${it.name}=${it.url}")} + } + } + """; + } + @Override + public void writeDependency(Writer writer, Dependency dependency) throws IOException { + writer.write( + String.format( + """ + project.dependencies.add("runtimeOnly", "%s:%s:%s") + """, + dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() + ) + ); + } + + @Override + public void writeDependencies(Writer writer, List dependencies, String projectName) throws IOException { + writer.write( + String.format( + """ + project("%s") { + plugins.withType() { + dependencies { + """, + projectName + ) + ); + for (Dependency dependency : dependencies) { + writer.write( + String.format( + """ + add("runtimeOnly", "%s:%s:%s") + """, + dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() + ) + ); + } + writer.write( + """ + } + } + } + """ + ); + } + + @Override + public void writeBuildFileLocation(Writer writer, Path buildFile) throws IOException { + writer.write( + String.format( + """ + rootProject.buildFileName = "%s" + """, + buildFile.getFileName() + ) + ); + } + + @Override + public String getBuildScriptFileName() { + return "build.gradle.kts"; + } + + @Override + public String getSettingsScriptFileName() { + return "settings.gradle.kts"; + } + }, + /** + * The writer dedicated to gradle files written in Groovy. + */ + GROOVY { + @Override + public String getInitScriptFileName() { + return "init.gradle"; + } + + @Override + String getListRepositoriesContent() { + return """ + allprojects { + tasks.register('cameltoolingShowRepos') { + repositories + .collect{it as MavenArtifactRepository} + .forEach{ println("${it.name}=${it.url}")} + } + } + """; + } + @Override + public void writeDependency(Writer writer, Dependency dependency) throws IOException { + writer.write( + String.format( + """ + project.dependencies.add('runtimeOnly', '%s:%s:%s') + """, + dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() + ) + ); + } + + @Override + public void writeDependencies(Writer writer, List dependencies, String projectName) throws IOException { + writer.write( + String.format( + """ + project('%s') { + plugins.withType(JavaPlugin.class) { + dependencies { + """, + projectName + ) + ); + for (Dependency dependency : dependencies) { + writer.write( + String.format( + """ + runtimeOnly '%s:%s:%s' + """, + dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() + ) + ); + } + writer.write( + """ + } + } + } + """ + ); + } + + @Override + public void writeBuildFileLocation(Writer writer, Path buildFile) throws IOException { + writer.write( + String.format( + """ + rootProject.buildFileName = '%s' + """, + buildFile.getFileName() + ) + ); + } + + @Override + public String getBuildScriptFileName() { + return "build.gradle"; + } + + @Override + public String getSettingsScriptFileName() { + return "settings.gradle"; + } + }; + + + /** + * Writes the given dependency to the given writer. + * + * @param writer the target writer + * @param dependency the dependency to write + * @throws IOException if the dependency could not be written + */ + public abstract void writeDependency(Writer writer, Dependency dependency) throws IOException; + + /** + * Writes the given dependencies of a specific project to the given writer. + * + * @param writer the target writer + * @param dependencies the dependencies to write + * @param projectName the target project name + * @throws IOException if the dependencies could not be written + */ + public abstract void writeDependencies(Writer writer, List dependencies, String projectName) throws IOException; + + /** + * Writes the location of the new build file. + * + * @param writer the target writer + * @param buildFile the new build file to configure. + * @throws IOException if the location of the build file could not be written + */ + public abstract void writeBuildFileLocation(Writer writer, Path buildFile) throws IOException; + + /** + * @return the name of the build script file + */ + public abstract String getBuildScriptFileName(); + + /** + * @return the name of the settings script file + */ + public abstract String getSettingsScriptFileName(); + + /** + * @return the name of the initialization script file + */ + public abstract String getInitScriptFileName(); + + /** + * @return the code allowing to list the repositories of a Gradle project + */ + abstract String getListRepositoriesContent(); + + /** + * Writes the code allowing to list the repositories of a Gradle project. + * + * @param writer the target writer + * @throws IOException if the code could not be written + */ + void writeListRepositoriesContent(Writer writer) throws IOException { + writer.write(getListRepositoriesContent()); + } + + /** + * @param vf the virtual file to check + * @return the writer corresponding to the given virtual file, or {@code null} if none could be found + */ + public static GradleFileWriter from(VirtualFile vf) { + for (GradleFileWriter writer : values()) { + if (vf.findChild(writer.getBuildScriptFileName()) != null || vf.findChild(writer.getSettingsScriptFileName()) != null) { + return writer; + } + } + return null; + } + + /** + * @param parent the parent directory to check + * @return the writer corresponding to the given parent directory, or {@code null} if none could be found + */ + public static GradleFileWriter from(Path parent) { + for (GradleFileWriter writer : values()) { + if (Files.exists(parent.resolve(writer.getBuildScriptFileName())) || Files.exists(parent.resolve(writer.getSettingsScriptFileName()))) { + return writer; + } + } + return null; + } +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/gradle/GradleUtil.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/gradle/GradleUtil.java new file mode 100644 index 00000000..f98300cd --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/gradle/GradleUtil.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.gradle; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vfs.VirtualFile; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; + +/** + * {@code GradleUtil} is a utility class for Gradle related tasks. + */ +public final class GradleUtil { + private static final Logger LOG = Logger.getInstance(GradleUtil.class); + private GradleUtil() { + } + + /** + * Scans for third party maven repositories in the Gradle scripts files of the project. + * + * @param vf the root directory of the project + * @return a map with repo id and url for each found repository. The map may be empty if no third party repository + */ + public static Map extractRepositoriesFomGradleProject(VirtualFile vf) { + File initFile = createListRepositoriesInitScript(vf); + if (initFile == null) { + return Map.of(); + } + Map answer = new LinkedHashMap<>(); + try (ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(new File(vf.getCanonicalPath())) + .connect(); + ByteArrayOutputStream stdOut = new ByteArrayOutputStream()) { + connection.newBuild().withArguments("--init-script", initFile.getPath(), "-q", "cameltoolingShowRepos") + .setStandardOutput(stdOut) + .setStandardError(stdOut) + .run(); + String[] lines = stdOut.toString().split("\n"); + for (String line : lines) { + String[] parts = line.split("="); + if (parts.length == 2) { + LOG.info("Found third party Maven repository id: " + parts[0] + " url:" + parts[1]); + answer.put(parts[0], parts[1]); + } + } + } catch (Exception e) { + LOG.warn("Error parsing Gradle project", e); + } + return answer; + } + + /** + * Creates a temporary init script file to list the repositories of the Gradle project. + * + * @param vf the root directory of the project + * @return the created init script file or {@code null} if the file could not be created, or it is not + * Gradle project + */ + private static File createListRepositoriesInitScript(VirtualFile vf) { + GradleFileWriter writer = GradleFileWriter.from(vf); + if (writer == null) { + return null; + } + + File initFile = Paths.get( + System.getProperty("java.io.tmpdir"), String.format("cameltooling.%s", writer.getInitScriptFileName()) + ).toFile(); + if (Files.exists(initFile.toPath()) && initFile.delete()) { + LOG.debug("Deleted old init file: " + initFile); + } + try (FileWriter fileWriter = new FileWriter(initFile, false)) { + writer.writeListRepositoriesContent(fileWriter); + } catch (IOException e) { + LOG.warn("Error parsing Gradle project", e); + return null; + } + return initFile; + } +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/inspection/AbstractCamelInspection.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/inspection/AbstractCamelInspection.java index 0a785693..4d4d1bd0 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/inspection/AbstractCamelInspection.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/inspection/AbstractCamelInspection.java @@ -49,10 +49,10 @@ public abstract class AbstractCamelInspection extends LocalInspectionTool { private boolean forceEnabled; - public AbstractCamelInspection() { + protected AbstractCamelInspection() { } - public AbstractCamelInspection(boolean forceEnabled) { + protected AbstractCamelInspection(boolean forceEnabled) { this.forceEnabled = forceEnabled; } @@ -119,7 +119,7 @@ private void validateSimple(@NotNull PsiElement element, final @NotNull Problems CamelService camelService = element.getProject().getService(CamelService.class); IElementType type = element.getNode().getElementType(); - LOG.trace("Element " + element + " of type: " + type + " to inspect simple: " + text); + LOG.trace("Element %s of type: %s to inspect simple: %s".formatted(element, type, text)); try { // need to use the classloader that can load classes from the camel-core @@ -146,8 +146,8 @@ private void validateSimple(@NotNull PsiElement element, final @NotNull Problems holder.registerProblem(element, msg); } } - } catch (Throwable e) { - LOG.warn("Error inspection Camel simple: " + text, e); + } catch (Exception e) { + LOG.warn("Error inspection Camel simple: %s".formatted(text), e); } } @@ -189,8 +189,8 @@ private void validateJSonPath(@NotNull PsiElement element, final @NotNull Proble holder.registerProblem(element, msg); } } - } catch (Throwable e) { - LOG.warn("Error inspection Camel jsonpath: " + text, e); + } catch (Exception e) { + LOG.warn("Error inspection Camel jsonpath: %s".formatted(text), e); } } @@ -209,7 +209,7 @@ private void validateEndpoint(@NotNull PsiElement element, final @NotNull Proble // camel catalog expects & as & when it parses so replace all & as & String camelQuery = text; - camelQuery = camelQuery.replaceAll("&", "&"); + camelQuery = camelQuery.replace("&", "&"); // strip up ending incomplete parameter if (camelQuery.endsWith("&") || camelQuery.endsWith("?")) { @@ -219,9 +219,7 @@ private void validateEndpoint(@NotNull PsiElement element, final @NotNull Proble boolean stringFormat = camelIdeaUtils.isFromStringFormatEndpoint(element); if (stringFormat) { // if the node is fromF or toF, then replace all %X with {{%X}} as we cannot parse that value - camelQuery = camelQuery.replaceAll("%s", "\\{\\{\\%s\\}\\}"); - camelQuery = camelQuery.replaceAll("%d", "\\{\\{\\%d\\}\\}"); - camelQuery = camelQuery.replaceAll("%b", "\\{\\{\\%b\\}\\}"); + camelQuery = camelQuery.replaceAll("(%[bds])", "{{$1}}"); } boolean consumerOnly = camelIdeaUtils.isConsumerEndpoint(element); @@ -238,8 +236,8 @@ private void validateEndpoint(@NotNull PsiElement element, final @NotNull Proble extractSetValue(result, result.getUnknown(), text, element, holder, isOnTheFly, new AbstractCamelInspection.UnknownErrorMsg()); extractSetValue(result, result.getNotConsumerOnly(), text, element, holder, isOnTheFly, new AbstractCamelInspection.NotConsumerOnlyErrorMsg()); extractSetValue(result, result.getNotProducerOnly(), text, element, holder, isOnTheFly, new AbstractCamelInspection.NotProducerOnlyErrorMsg()); - } catch (Throwable e) { - LOG.warn("Error inspecting Camel endpoint: " + text, e); + } catch (Exception e) { + LOG.warn("Error inspecting Camel endpoint: %s".formatted(text), e); } } @@ -271,7 +269,7 @@ private static class BooleanErrorMsg implements CamelAnnotatorEndpointMessage entry) { String name = entry.getKey(); - boolean empty = entry.getValue() == null || entry.getValue().length() == 0; + boolean empty = entry.getValue() == null || entry.getValue().isEmpty(); if (empty) { return name + " has empty boolean value"; } else { @@ -381,7 +379,6 @@ public boolean isWarnLevel() { * * @return the summary, or empty if no validation errors */ - @SuppressWarnings("unchecked") private String summaryErrorMessage(EndpointValidationResult result, T entry, CamelAnnotatorEndpointMessage msg) { if (result.getIncapable() != null) { return "Incapable of parsing uri: " + result.getIncapable(); diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelMavenVersionManager.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/CamelMavenVersionManager.java similarity index 90% rename from camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelMavenVersionManager.java rename to camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/CamelMavenVersionManager.java index cf21a4bf..50882e02 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelMavenVersionManager.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/CamelMavenVersionManager.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.cameltooling.idea.service; +package com.github.cameltooling.idea.maven; import java.io.IOException; import java.io.InputStream; @@ -22,30 +22,25 @@ import java.util.Enumeration; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; import org.apache.camel.catalog.VersionManager; /** - * A copy of {@link org.apache.camel.catalog.maven.MavenVersionManager} as IDEA cannot use this class at runtime, + * A copy of {@code org.apache.camel.catalog.maven.MavenVersionManager} as IDEA cannot use this class at runtime, * so we use a simpler copy here. */ -class CamelMavenVersionManager implements VersionManager { +public class CamelMavenVersionManager implements VersionManager { /** * The logger. */ private static final Logger LOG = Logger.getInstance(CamelMavenVersionManager.class); - private final MavenArtifactRetrieverContext context = new MavenArtifactRetrieverContext(); + private final MavenArtifactRetrieverContext context; private String version; private String runtimeProviderVersion; - /** - * To add a 3rd party Maven repository. - * - * @param name the repository name - * @param url the repository url - */ - void addMavenRepository(String name, String url) { - context.addMavenRepository(name, url); + public CamelMavenVersionManager(Project project) { + this.context = new MavenArtifactRetrieverContext(project); } @Override diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/MavenArtifactRetrieverContext.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/MavenArtifactRetrieverContext.java similarity index 91% rename from camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/MavenArtifactRetrieverContext.java rename to camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/MavenArtifactRetrieverContext.java index e415c4a9..3de2d226 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/MavenArtifactRetrieverContext.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/MavenArtifactRetrieverContext.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.github.cameltooling.idea.service; +package com.github.cameltooling.idea.maven; import java.io.Closeable; import java.io.IOException; @@ -28,19 +28,21 @@ import java.util.Map; import com.github.cameltooling.idea.util.ArtifactCoordinates; +import com.intellij.openapi.project.Project; import org.apache.camel.tooling.maven.MavenArtifact; import org.apache.camel.tooling.maven.MavenDownloader; import org.apache.camel.tooling.maven.MavenDownloaderImpl; import org.apache.camel.tooling.maven.MavenGav; import org.apache.camel.tooling.maven.MavenResolutionException; +import static com.github.cameltooling.idea.maven.MavenUtil.scanThirdPartyMavenRepositories; + /** * {@code MavenArtifactRetrieverContext} is meant to be used to download artifacts from maven repositories. * All the downloaded artifacts and their dependencies are automatically added to the underlying * {@code URLClassLoader} to be able to have access to the local path of the artifacts. */ public class MavenArtifactRetrieverContext implements Closeable { - private final MavenDownloader downloader; private final Map repositories = new LinkedHashMap<>(); private final MavenClassLoader classLoader = new MavenClassLoader(); @@ -51,13 +53,18 @@ public MavenArtifactRetrieverContext() { ((MavenDownloaderImpl) downloader).build(); } + public MavenArtifactRetrieverContext(Project project) { + this(); + scanThirdPartyMavenRepositories(project).forEach(this::addMavenRepository); + } + /** * To add a 3rd party Maven repository. * * @param name the repository name * @param url the repository url */ - public void addMavenRepository(String name, String url) { + private void addMavenRepository(String name, String url) { repositories.put(name, url); } diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/MavenUtil.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/MavenUtil.java new file mode 100644 index 00000000..5486d761 --- /dev/null +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/maven/MavenUtil.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.maven; + +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.github.cameltooling.idea.gradle.GradleUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.apache.maven.model.Model; +import org.apache.maven.model.Repository; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.jetbrains.annotations.NotNull; + +/** + * {@code MavenUtil} is a utility class for Maven related tasks. + */ +public final class MavenUtil { + private static final Logger LOG = Logger.getInstance(MavenUtil.class); + + private MavenUtil() { + } + + /** + * Scans for third party maven repositories in the root pom.xml file of the project. + * + * @return a map with repo id and url for each found repository. The map may be empty if no third party repository + * is defined in the pom.xml file + */ + static @NotNull Map scanThirdPartyMavenRepositories(Project project) { + VirtualFile vf = ProjectUtil.guessProjectDir(project); + if (vf != null) { + VirtualFile pom = vf.findFileByRelativePath("pom.xml"); + if (pom == null) { + return GradleUtil.extractRepositoriesFomGradleProject(vf); + } + return extractFomPomFile(pom); + } + + return Map.of(); + } + + /** + * Scans for third party maven repositories in the root pom.xml file of the project. + * + * @param vf the root directory of the project + * @return a map with repo id and url for each found repository. The map may be empty if no third party repository + */ + private static Map extractFomPomFile(VirtualFile vf) { + Map answer = new LinkedHashMap<>(); + try (InputStream is = vf.getInputStream()) { + final Model model = new MavenXpp3Reader().read(is); + for (Repository repository : model.getRepositories()) { + String id = repository.getId(); + String url = repository.getUrl(); + if (id != null && url != null) { + LOG.info("Found third party Maven repository id: " + id + " url:" + url); + answer.put(id, url); + } + } + } catch (Exception e) { + LOG.warn("Error parsing Maven pon.xml file", e); + } + return answer; + } +} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerPatcher.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerPatcher.java index cf77cc5b..14cbb603 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerPatcher.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerPatcher.java @@ -20,7 +20,6 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.io.Writer; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; @@ -36,6 +35,7 @@ import java.util.StringJoiner; import java.util.function.Function; +import com.github.cameltooling.idea.gradle.GradleFileWriter; import com.github.cameltooling.idea.runner.CamelQuarkusRunConfigurationType; import com.github.cameltooling.idea.runner.CamelRunConfiguration; import com.github.cameltooling.idea.runner.CamelSpringBootRunConfigurationType; @@ -44,7 +44,7 @@ import com.github.cameltooling.idea.service.CamelProjectPreferenceService; import com.github.cameltooling.idea.service.CamelRuntime; import com.github.cameltooling.idea.service.CamelService; -import com.github.cameltooling.idea.service.MavenArtifactRetrieverContext; +import com.github.cameltooling.idea.maven.MavenArtifactRetrieverContext; import com.github.cameltooling.idea.util.ArtifactCoordinates; import com.intellij.execution.Executor; import com.intellij.execution.JavaRunConfigurationBase; @@ -264,7 +264,7 @@ private static void patchJavaParametersOnDebug(Project project, ExecutionMode mo notify(project, MessageType.INFO, "Camel Debugger has been added automatically with success."); LOG.debug("The camel-debug has been added with success"); } catch (Exception e) { - LOG.error("Could not add the Camel Debugger automatically", e); + LOG.warn("Could not add the Camel Debugger automatically", e); notify(project, MessageType.ERROR, "Camel Debugger could not be added automatically."); } } else { @@ -407,22 +407,20 @@ private static List generateBuildFilesWithCamelDebugger(ExecutionMode mode final ParametersList parametersList = parameters.getProgramParametersList(); List listParameters = parametersList.getParameters(); final int indexBuildFile = Math.max(listParameters.indexOf("-b"), listParameters.indexOf("--build-file")); - final GradleBuildWriter writer; + final GradleFileWriter writer; final String targetBuildFileName; if (indexBuildFile == -1) { - if (Files.exists(parent.resolve("build.gradle.kts")) || Files.exists(parent.resolve("settings.gradle.kts"))) { - targetBuildFileName = "build.gradle.kts"; - writer = GradleBuildWriter.KOTLIN; - } else { - targetBuildFileName = "build.gradle"; - writer = GradleBuildWriter.GROOVY; + writer = GradleFileWriter.from(parent); + if (writer == null) { + return List.of(); } + targetBuildFileName = writer.getBuildScriptFileName(); } else { targetBuildFileName = parametersList.get(indexBuildFile + 1); if (targetBuildFileName.endsWith(".kts")) { - writer = GradleBuildWriter.KOTLIN; + writer = GradleFileWriter.KOTLIN; } else { - writer = GradleBuildWriter.GROOVY; + writer = GradleFileWriter.GROOVY; } } @@ -445,12 +443,12 @@ private static List generateBuildFilesWithCamelDebugger(ExecutionMode mode if (parameter.split(":").length >= expectedLength) { added = true; String projectName = parameter.substring(0, parameter.lastIndexOf(':')); - writer.write(fileWriter, dependencies, projectName); + writer.writeDependencies(fileWriter, dependencies, projectName); } } if (!added) { for (Dependency dependency : dependencies) { - writer.write(fileWriter, dependency); + writer.writeDependency(fileWriter, dependency); } } } @@ -464,17 +462,13 @@ private static List generateBuildFilesWithCamelDebugger(ExecutionMode mode final int indexSettingsFile = Math.max(listParameters.indexOf("-c"), listParameters.indexOf("--settings-file")); final String targetSettingsFileName; if (indexSettingsFile == -1) { - if (writer == GradleBuildWriter.KOTLIN) { - targetSettingsFileName = "settings.gradle.kts"; - } else { - targetSettingsFileName = "settings.gradle"; - } + targetSettingsFileName = writer.getSettingsScriptFileName(); } else { targetSettingsFileName = parametersList.get(indexSettingsFile + 1); } Path settingsFile = createGeneratedFile(parent, targetSettingsFileName); try (FileWriter fileWriter = new FileWriter(settingsFile.toFile(), true)) { - writer.write(fileWriter, buildFile); + writer.writeBuildFileLocation(fileWriter, buildFile); } if (indexSettingsFile == -1) { parametersList.add("-c"); @@ -587,7 +581,7 @@ private static void autoDownloadCamelDebugger(Project project, JavaParameters pa ); } } - for (URL url : downloadCamelDebugger(runtime, versionRuntime)) { + for (URL url : downloadCamelDebugger(project, runtime, versionRuntime)) { try { parameters.getClassPath().add(new File(url.toURI())); } catch (URISyntaxException e) { @@ -599,6 +593,7 @@ private static void autoDownloadCamelDebugger(Project project, JavaParameters pa /** * Downloads the Camel Debugger and its dependencies corresponding to the given runtime. * + * @param project the project for which the Camel Debugger needs to be downloaded. * @param runtime the target runtime for which the Camel Debugger is downloaded. * @param version the version of the Camel Debugger to download. * @return the list of {@link URL} corresponding to the location of the Camel Debugger and its dependencies that have @@ -606,8 +601,8 @@ private static void autoDownloadCamelDebugger(Project project, JavaParameters pa * @throws IOException if an error occurs while downloading the Camel Debugger and its dependencies. */ @NotNull - private static List downloadCamelDebugger(@NotNull CamelRuntime runtime, @NotNull String version) throws IOException { - try (MavenArtifactRetrieverContext context = new MavenArtifactRetrieverContext()) { + private static List downloadCamelDebugger(Project project, @NotNull CamelRuntime runtime, @NotNull String version) throws IOException { + try (MavenArtifactRetrieverContext context = new MavenArtifactRetrieverContext(project)) { ArtifactCoordinates debugArtifact = runtime.getDebugArtifact(); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Trying to download %s %s with all its dependencies", debugArtifact, version)); @@ -644,6 +639,7 @@ private static void addCamelDebuggerProperties(ParametersList parametersList) { */ private static void addCamelDebuggerEnvironmentVariable(JavaParameters parameters) { parameters.getEnv().put("CAMEL_DEBUGGER_SUSPEND", "true"); + parameters.getEnv().put("CAMEL_MAIN_DEBUGGING", "true"); } /** @@ -711,7 +707,10 @@ void configureCamelDebugger(JavaParameters parameters) { @Override void addRequiredParameters(JavaParameters parameters) { - super.addRequiredMavenGoals(parameters); + // Avoid to compile as it can prevent the automatic addition of the Camel Debugger from working properly + // Indeed otherwise, the addition of the Camel Debugger to a custom pom file is simply ignored + parameters.getProgramParametersList().addAt(0, "clean"); + parameters.getProgramParametersList().addAt(1, runtime.getPluginGoal()); } @Override @@ -845,7 +844,7 @@ void autoAddCamelDebugger(Project project, JavaParameters parameters, String ver /** * The corresponding Camel Runtime. */ - private final CamelRuntime runtime; + protected final CamelRuntime runtime; /** * Constructs a {@code ExecutionMode} with the given Camel Runtime. @@ -999,157 +998,4 @@ private void addRequiredMavenGoals(JavaParameters parameters) { parameters.getProgramParametersList().addAt(2, runtime.getPluginGoal()); } } - - /** - * The supported writer of gradle build file. - */ - private enum GradleBuildWriter { - - /** - * The writer dedicated to gradle build file written in Groovy. - */ - GROOVY { - @Override - void write(Writer writer, Dependency dependency) throws IOException { - writer.write( - String.format( - """ - project.dependencies.add('runtimeOnly', '%s:%s:%s') - """, - dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() - ) - ); - } - - @Override - void write(Writer writer, List dependencies, String projectName) throws IOException { - writer.write( - String.format( - """ - project('%s') { - plugins.withType(JavaPlugin.class) { - dependencies { - """, - projectName - ) - ); - for (Dependency dependency : dependencies) { - writer.write( - String.format( - """ - runtimeOnly '%s:%s:%s' - """, - dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() - ) - ); - } - writer.write( - """ - } - } - } - """ - ); - } - - @Override - void write(Writer writer, Path buildFile) throws IOException { - writer.write( - String.format( - """ - rootProject.buildFileName = '%s' - """, - buildFile.getFileName() - ) - ); - } - }, - /** - * The writer dedicated to gradle build file written in Kotlin. - */ - KOTLIN { - @Override - void write(Writer writer, Dependency dependency) throws IOException { - writer.write( - String.format( - """ - project.dependencies.add("runtimeOnly", "%s:%s:%s") - """, - dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() - ) - ); - } - - @Override - void write(Writer writer, List dependencies, String projectName) throws IOException { - writer.write( - String.format( - """ - project("%s") { - plugins.withType() { - dependencies { - """, - projectName - ) - ); - for (Dependency dependency : dependencies) { - writer.write( - String.format( - """ - add("runtimeOnly", "%s:%s:%s") - """, - dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() - ) - ); - } - writer.write( - """ - } - } - } - """ - ); - } - - @Override - void write(Writer writer, Path buildFile) throws IOException { - writer.write( - String.format( - """ - rootProject.buildFileName = "%s" - """, - buildFile.getFileName() - ) - ); - } - }; - - /** - * Writes the given dependency to the given writer. - * - * @param writer the target writer - * @param dependency the dependency to write - * @throws IOException if the dependency could not be written - */ - abstract void write(Writer writer, Dependency dependency) throws IOException; - - /** - * Writes the given dependencies of a specific project to the given writer. - * - * @param writer the target writer - * @param dependencies the dependencies to write - * @param projectName the target project name - * @throws IOException if the dependencies could not be written - */ - abstract void write(Writer writer, List dependencies, String projectName) throws IOException; - - /** - * Writes the location of the new build file. - * - * @param writer the target writer - * @param buildFile the new build file to configure. - * @throws IOException if the location of the build file could not be written - */ - abstract void write(Writer writer, Path buildFile) throws IOException; - } } diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerSession.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerSession.java index 7e478649..fe785cc9 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerSession.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/CamelDebuggerSession.java @@ -878,6 +878,7 @@ private String getBreakpointId(@NotNull PsiElement breakpointTag) { if (basePath != null && url.startsWith(basePath)) { sourceLocations.add(String.format("file:%s", url.substring(basePath.length() + 1))); // file:file.xml } + sourceLocations.add(virtualFile.getName()); // file.xml } else { //Then it must be a Jar sourceLocations = List.of(String.format("classpath:%s", url.substring(url.lastIndexOf("!") + 2))); } @@ -897,8 +898,9 @@ private String getBreakpointId(@NotNull PsiElement breakpointTag) { } else { relativePath = virtualFile.getName(); } - sourceLocations.add(relativePath); // file.xml - sourceLocations.add(String.format("file:%s", relativePath)); // file:file.xml + sourceLocations.add(relativePath); // file.java + sourceLocations.add(String.format("file:%s", relativePath)); // file:file.java + sourceLocations.add(virtualFile.getName()); // file.java } break; default: // noop diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/ui/CamelDebuggerEvaluationDialog.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/ui/CamelDebuggerEvaluationDialog.java index c0c73d30..4de81efe 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/ui/CamelDebuggerEvaluationDialog.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/runner/debugger/ui/CamelDebuggerEvaluationDialog.java @@ -74,8 +74,6 @@ import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -83,7 +81,7 @@ public class CamelDebuggerEvaluationDialog extends DialogWrapper { public static final DataKey KEY = DataKey.create("CAMEL_DEBUGGER_EVALUATION_DIALOG"); - //can not use new SHIFT_DOWN_MASK etc because in this case ActionEvent modifiers do not match + //cannot use new SHIFT_DOWN_MASK etc. because in this case ActionEvent modifiers do not match private static final int ADD_WATCH_MODIFIERS = (SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK) | InputEvent.SHIFT_MASK; static KeyStroke addWatchKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, ADD_WATCH_MODIFIERS); diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelCatalogService.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelCatalogService.java index f0d80445..9d559440 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelCatalogService.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelCatalogService.java @@ -16,9 +16,8 @@ */ package com.github.cameltooling.idea.service; -import java.util.Map; - import com.github.cameltooling.idea.catalog.CamelCatalogProvider; +import com.github.cameltooling.idea.maven.CamelMavenVersionManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import org.apache.camel.catalog.CamelCatalog; @@ -100,16 +99,12 @@ private void updateRuntimeProvider(CamelCatalog catalog) { * Loads a specific Camel version into the Catalog to use. * * @param version the version to load - * @param repos any third party maven repositories */ - boolean loadVersion(@NotNull String version, @NotNull Map repos) { + boolean loadVersion(@NotNull String version) { // we should load a new version of the catalog, and therefore must discard the old version dispose(); // use maven to be able to load the version dynamic - CamelMavenVersionManager maven = new CamelMavenVersionManager(); - - // add support for the maven repos - repos.forEach(maven::addMavenRepository); + CamelMavenVersionManager maven = new CamelMavenVersionManager(project); get().setVersionManager(maven); boolean loaded = get().getVersionManager().loadVersion(version); diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelJBangService.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelJBangService.java index 24f5026d..478471d8 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelJBangService.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelJBangService.java @@ -30,6 +30,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.github.cameltooling.idea.maven.MavenArtifactRetrieverContext; import com.github.cameltooling.idea.util.ArtifactCoordinates; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.KillableColoredProcessHandler; diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelService.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelService.java index 7060838e..6fff95e6 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelService.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/CamelService.java @@ -16,8 +16,6 @@ */ package com.github.cameltooling.idea.service; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -25,19 +23,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import javax.swing.*; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; +import javax.swing.Icon; import com.github.cameltooling.idea.catalog.CamelCatalogProvider; import com.github.cameltooling.idea.util.ArtifactCoordinates; @@ -53,19 +45,15 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.messages.Topic; import org.apache.camel.catalog.CamelCatalog; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static com.github.cameltooling.idea.service.XmlUtils.getChildNodeByTagName; -import static com.github.cameltooling.idea.service.XmlUtils.loadDocument; import static org.apache.camel.catalog.impl.CatalogHelper.loadText; /** @@ -533,8 +521,7 @@ private boolean notifyNewCamelCatalogVersionLoaded(boolean notifyLoaded) { private boolean downloadNewCamelCatalogVersion(@NotNull CamelCatalogProvider provider, @NotNull String version) { // find out the third party maven repositories final CamelCatalogService catalogService = getCamelCatalogService(); - boolean loaded = catalogService.loadVersion(version, scanThirdPartyMavenRepositories()) - && provider.loadRuntimeProviderVersion(project); + boolean loaded = catalogService.loadVersion(version) && provider.loadRuntimeProviderVersion(project); if (!loaded) { // always notify if download was not possible camelVersionNotification = CAMEL_NOTIFICATION_GROUP.createNotification( @@ -604,48 +591,6 @@ private void notifyForMissingJsonSchemas(List missingJSonSchemas) { } } - /** - * Scans for third party maven repositories in the root pom.xml file of the project. - * - * @return a map with repo id and url for each found repository. The map may be empty if no third party repository - * is defined in the pom.xml file - */ - private @NotNull Map scanThirdPartyMavenRepositories() { - Map answer = new LinkedHashMap<>(); - - VirtualFile vf = ProjectUtil.guessProjectDir(project); - if (vf != null) { - vf = vf.findFileByRelativePath("pom.xml"); - } - if (vf != null) { - try (InputStream is = vf.getInputStream()) { - Document dom = loadDocument(is, false); - NodeList list = dom.getElementsByTagName("repositories"); - if (list != null && list.getLength() == 1) { - Node repos = list.item(0); - if (repos instanceof Element) { - Element element = (Element) repos; - list = element.getElementsByTagName("repository"); - for (int i = 0; i < list.getLength(); i++) { - Node node = list.item(i); - // grab id and url - Node id = getChildNodeByTagName(node, "id"); - Node url = getChildNodeByTagName(node, "url"); - if (id != null && url != null) { - LOG.info("Found third party Maven repository id: " + id.getTextContent() + " url:" + url.getTextContent()); - answer.put(id.getTextContent(), url.getTextContent()); - } - } - } - } - } catch (Exception e) { - LOG.warn("Error parsing Maven pon.xml file", e); - } - } - - return answer; - } - /** * Adds any discovered third party Camel components from the dependency. * diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/KameletService.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/KameletService.java index 8a598789..bc499f28 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/KameletService.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/KameletService.java @@ -132,6 +132,16 @@ public void after(@NotNull List events) { ); } + /** + * Check if the given name corresponds to a Kamelet that can be used in a consumer endpoint. + * @param name the name of the Kamelet to check. + * @return {@code true} if the Kamelet can be used in a consumer endpoint, {@code false} otherwise. + */ + public boolean isConsumer(@NotNull String name) { + Kamelet kamelet = getKamelets().get(name); + return kamelet != null && KAMELET_SOURCE_TYPE.equals(kamelet.getType()); + } + /** * Gives the name of the Kamelets that can be used in a consumer endpoint. * diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/XmlErrorHandler.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/XmlErrorHandler.java deleted file mode 100644 index 3bfd3121..00000000 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/XmlErrorHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 com.github.cameltooling.idea.service; - -import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -class XmlErrorHandler implements ErrorHandler { - - XmlErrorHandler() { - } - - @Override - public void warning(SAXParseException exception) throws SAXException { - throw new SAXException(saxMsg(exception)); - } - - @Override - public void error(SAXParseException exception) throws SAXException { - throw new SAXException(saxMsg(exception)); - } - - @Override - public void fatalError(SAXParseException exception) throws SAXException { - throw new SAXException(saxMsg(exception)); - } - - private String saxMsg(SAXParseException e) { - return "Line: " + e.getLineNumber() + ", Column: " + e.getColumnNumber() + ", Error: " + e.getMessage(); - } -} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/XmlUtils.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/XmlUtils.java deleted file mode 100644 index 7c05811b..00000000 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/XmlUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 com.github.cameltooling.idea.service; - -import java.io.InputStream; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.jetbrains.annotations.NotNull; - -/** - * XML and DOM utilities. - */ -final class XmlUtils { - - private XmlUtils() { - } - - /** - * Loads the input stream into a DOM - * - * @param is the input stream - * @param validating whether to validate or not - * @return the DOM - */ - static @NotNull Document loadDocument(@NotNull InputStream is, boolean validating) throws Exception { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); - if (validating) { - documentBuilder.setErrorHandler(new XmlErrorHandler()); - } - dbf.setValidating(validating); - return documentBuilder.parse(is); - } - - /** - * Gets a child node by the given name. - * - * @param node the node - * @param name the name of the child node to find and return - * @return the child node, or null if none found - */ - static Node getChildNodeByTagName(Node node, String name) { - NodeList children = node.getChildNodes(); - if (children != null && children.getLength() > 0) { - for (int i = 0; i < children.getLength(); i++) { - Node child = children.item(i); - if (child instanceof Element) { - Element element = (Element) child; - if (name.equals(element.getTagName())) { - return element; - } - } - } - } - return null; - } -} diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/extension/camel/JavaCamelIdeaUtils.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/extension/camel/JavaCamelIdeaUtils.java index 374425a5..501dee90 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/extension/camel/JavaCamelIdeaUtils.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/service/extension/camel/JavaCamelIdeaUtils.java @@ -32,6 +32,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.JavaResolveResult; import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiCallExpression; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; @@ -55,7 +56,6 @@ import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.impl.source.PsiClassReferenceType; import com.intellij.psi.search.searches.ClassInheritorsSearch; -import com.intellij.psi.util.ClassUtil; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.LightVirtualFile; @@ -94,7 +94,7 @@ public class JavaCamelIdeaUtils extends CamelIdeaUtils implements CamelIdeaUtils "kamelet", "step", "transacted", "saga", "route", "resequence", "policy", "onException", "onCompletion", "from", "rest", "restConfiguration"); /** - * Name of the methods corresponding to root element of sub DSL. + * Name of the methods corresponding to the root element of sub DSL. */ private static final Set SUB_DSL_ROOTS = Set.of("expression", "dataFormat"); /** @@ -122,6 +122,23 @@ public class JavaCamelIdeaUtils extends CamelIdeaUtils implements CamelIdeaUtils "org.apache.camel.spring.SpringRouteBuilder", "org.apache.camel.builder.endpoint.EndpointRouteBuilder" ); + private static final List BEAN_ANNOTATIONS = Arrays.asList( + "org.springframework.stereotype.Component", + "org.springframework.stereotype.Service", + "org.springframework.stereotype.Repository", + "javax.inject.Named", + "javax.inject.Singleton", + "javax.enterprise.context.ApplicationScoped", + "javax.enterprise.context.SessionScoped", + "javax.enterprise.context.ConversationScoped", + "javax.enterprise.context.RequestScoped", + "jakarta.inject.Named", + "jakarta.inject.Singleton", + "jakarta.enterprise.context.ApplicationScoped", + "jakarta.enterprise.context.SessionScoped", + "jakarta.enterprise.context.ConversationScoped", + "jakarta.enterprise.context.RequestScoped" + ); @Override public boolean isCamelFile(PsiFile file) { @@ -216,10 +233,10 @@ public boolean isCamelExpressionUsedAsPredicate(PsiElement element, String langu // okay dive into the psi and find out which EIP are using the simple PsiElement child = call.getFirstChild(); - if (child instanceof PsiReferenceExpression) { + if (child instanceof PsiReferenceExpression psiReferenceExpression) { // this code is needed as it may be used as a method call as a parameter and this requires // a bit of psi code to unwrap the right elements. - PsiExpression exp = ((PsiReferenceExpression) child).getQualifierExpression(); + PsiExpression exp = psiReferenceExpression.getQualifierExpression(); if (exp == null) { // okay it was not a direct method call, so see if it was passed in as a parameter instead (expression list) element = element.getParent(); @@ -230,8 +247,8 @@ public boolean isCamelExpressionUsedAsPredicate(PsiElement element, String langu exp = PsiTreeUtil.getParentOfType(element.getParent(), PsiMethodCallExpression.class); } } - if (exp instanceof PsiMethodCallExpression) { - PsiMethod method = ((PsiMethodCallExpression) exp).resolveMethod(); + if (exp instanceof PsiMethodCallExpression psiMethodCallExpression) { + PsiMethod method = psiMethodCallExpression.resolveMethod(); if (method != null) { String name = method.getName(); return Arrays.asList(PREDICATE_EIPS).contains(name); @@ -299,7 +316,7 @@ public boolean isFromStringFormatEndpoint(PsiElement element) { public boolean acceptForAnnotatorOrInspection(PsiElement element) { // skip XML limit on siblings if (!IdeaUtils.getService().isFromFileType(element, "xml")) { - // for programming languages you can have complex structures with concat which we don't support yet + // for programming languages you can have complex structures with concat which we don't support it yet. // we currently only support oneliner, so check how many siblings the element has (it has 1 with ending parenthesis which is okay) return countSiblings(element) <= 1; } @@ -310,8 +327,8 @@ public boolean acceptForAnnotatorOrInspection(PsiElement element) { public PsiClass getBeanClass(PsiElement element) { final PsiElement beanPsiElement = getPsiElementForCamelBeanMethod(element); if (beanPsiElement != null) { - if (beanPsiElement instanceof PsiClass) { - return (PsiClass) beanPsiElement; + if (beanPsiElement instanceof PsiClass psiClass) { + return psiClass; } PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.findChildOfType(beanPsiElement, PsiJavaCodeReferenceElement.class); @@ -372,19 +389,22 @@ public boolean isPlaceForEndpointUri(PsiElement location) { } /** - * @return the {@link PsiClass} for the matching bean name by looking for classes annotated with spring Component, Service or Repository + * @return the {@link PsiClass} for the matching bean name by looking for classes annotated with + * Spring Component, Service or Repository or Quarkus javax or jakarta annotations. */ private Optional searchForMatchingBeanClass(String beanName, Project project) { final JavaClassUtils javaClassUtils = JavaClassUtils.getService(); - return javaClassUtils.findBeanClassByName(beanName, "org.springframework.stereotype.Component", project) - .or(() -> javaClassUtils.findBeanClassByName(beanName, "org.springframework.stereotype.Service", project)) - .or(() -> javaClassUtils.findBeanClassByName(beanName, "org.springframework.stereotype.Repository", project)); + + return BEAN_ANNOTATIONS + .stream() + .map(annotation -> javaClassUtils.findBeanClassByName(beanName, annotation, project)) + .flatMap(Optional::stream) + .findFirst(); } private List findEndpoints(Module module, Predicate uriCondition, Predicate elementCondition) { PsiManager manager = PsiManager.getInstance(module.getProject()); - //TODO: use IdeaUtils.ROUTE_BUILDER_OR_EXPRESSION_CLASS_QUALIFIED_NAME somehow - PsiClass routeBuilderClass = ClassUtil.findPsiClass(manager, "org.apache.camel.builder.RouteBuilder"); + PsiClass routeBuilderClass = IdeaUtils.findRouteBuilderClass(manager); List results = new ArrayList<>(); if (routeBuilderClass != null) { @@ -394,8 +414,7 @@ private List findEndpoints(Module module, Predicate uriCondi Collection literals = PsiTreeUtil.findChildrenOfType(routeBuilder, PsiLiteralExpression.class); for (PsiLiteralExpression literal : literals) { Object val = literal.getValue(); - if (val instanceof String) { - String endpointUri = (String) val; + if (val instanceof String endpointUri) { if (uriCondition.test(endpointUri) && elementCondition.test(literal)) { results.add(literal); } @@ -436,7 +455,7 @@ private void format(PsiFile file, Document document, int startOffset, int endOff final VirtualFile vFile = FileDocumentManager.getInstance().getFile(document); if ((vFile == null || vFile instanceof LightVirtualFile) && !ApplicationManager.getApplication().isUnitTestMode()) { // we assume that control flow reaches this place when the document is backed by a "virtual" file so any changes made by - // a formatter affect only PSI and it is out of sync with a document text + // a formatter affect only PSI, and it is out of sync with a document text return; } @@ -638,11 +657,11 @@ private static boolean testMethod(PsiFile file, int offset, Predicate * This class is only for Camel related IDEA APIs. If you need only IDEA APIs then use {@link IdeaUtils} instead. */ +@Service public final class CamelIdeaUtils implements Disposable { public static final String[] CAMEL_FILE_EXTENSIONS = {"java", "xml", "yaml", "yml"}; @@ -192,7 +193,7 @@ public boolean isCamelExpressionOrLanguage(PsiClass clazz) { } /** - * Certain elements should be skipped for endpoint validation such as ActiveMQ brokerURL property and others. + * Certain elements should be skipped for endpoint validation, such as ActiveMQ brokerURL property and others. */ public boolean skipEndpointValidation(PsiElement element) { return enabledExtensions.stream() @@ -264,10 +265,9 @@ public List findEndpointDeclarations(Module module, Predicate - * This class is only for IDEA APIs. If you need Camel related APIs as well then use {@link CamelIdeaUtils} instead. + * This class is only for IDEA APIs. If you need Camel related APIs as well, use {@link CamelIdeaUtils} instead. */ +@Service public final class IdeaUtils implements Disposable { - private static final List ROUTE_BUILDER_OR_EXPRESSION_CLASS_QUALIFIED_NAME = Arrays.asList( - "org.apache.camel.builder.RouteBuilder", "org.apache.camel.builder.BuilderSupport", - "org.apache.camel.model.ProcessorDefinition", "org.apache.camel.model.language.ExpressionDefinition"); + private static final List ROUTE_BUILDER_OR_EXPRESSION_CLASS_QUALIFIED_NAME = List.of( + "org.apache.camel.builder.endpoint.EndpointRouteBuilder", + "org.apache.camel.builder.RouteBuilder", + "org.apache.camel.builder.BuilderSupport", + "org.apache.camel.model.ProcessorDefinition", + "org.apache.camel.model.language.ExpressionDefinition" + ); private final List enabledExtensions; private IdeaUtils() { enabledExtensions = Arrays.stream(IdeaUtilsExtension.EP_NAME.getExtensions()) .filter(IdeaUtilsExtension::isExtensionEnabled) - .collect(Collectors.toList()); + .toList(); } public static IdeaUtils getService() { @@ -124,9 +130,9 @@ public String extractTextFromElement(PsiElement element) { * Extract the text value from the {@link PsiElement} from any of the support languages this plugin works with. * * @param element the element - * @param fallBackToGeneric if could find any of the supported languages fallback to generic if true + * @param fallBackToGeneric if it could find any of the supported language-fallbacks to generic if true * @param concatString concatenated the string if it wrapped - * @param stripWhitespace + * @param stripWhitespace strip whitespaces * @return the text or null if the element is not a text/literal kind. */ @Nullable @@ -156,7 +162,7 @@ public String extractTextFromElement(PsiElement element, boolean fallBackToGener } /** - * Is the element from a java setter method (eg setBrokerURL) or from a XML configured bean style + * Is the element from a java setter method (e.g., setBrokerURL) or from an XML configured bean style * configuration using property element. */ public boolean isElementFromSetterProperty(@NotNull PsiElement element, @NotNull String setter) { @@ -181,21 +187,21 @@ public boolean isElementFromAnnotation(@NotNull PsiElement element, @NotNull Str * Is the element from Java language */ public boolean isJavaLanguage(PsiElement element) { - return element != null && PsiUtil.getNotAnyLanguage(element.getNode()).is(JavaLanguage.INSTANCE); + return element != null && PsiUtilCore.getNotAnyLanguage(element.getNode()).is(JavaLanguage.INSTANCE); } /** * Is the element from XML language */ public boolean isXmlLanguage(PsiElement element) { - return element != null && PsiUtil.getNotAnyLanguage(element.getNode()).is(XMLLanguage.INSTANCE); + return element != null && PsiUtilCore.getNotAnyLanguage(element.getNode()).is(XMLLanguage.INSTANCE); } /** * Is the element from YAML language */ public boolean isYamlLanguage(PsiElement element) { - return element != null && PsiUtil.getNotAnyLanguage(element.getNode()).is(YAMLLanguage.INSTANCE); + return element != null && PsiUtilCore.getNotAnyLanguage(element.getNode()).is(YAMLLanguage.INSTANCE); } /** @@ -207,8 +213,8 @@ public boolean isFromFileType(PsiElement element, @NotNull String... extensions) } PsiFile file; - if (element instanceof PsiFile) { - file = (PsiFile) element; + if (element instanceof PsiFile psiFile) { + file = psiFile; } else { file = PsiTreeUtil.getParentOfType(element, PsiFile.class); } @@ -255,7 +261,7 @@ public boolean isFromFileType(PsiElement element, @NotNull String... extensions) } /** - * Is the given class or any of its super classes a class with the qualified name. + * Is the given class or any of its superclasses a class with the qualified name. * * @param target the class * @param fqnClassName the class name to match @@ -273,11 +279,11 @@ private static boolean isClassOrParentOf(@Nullable PsiClass target, @NotNull Str } /** - * Is the element from a constructor call with the given constructor name (eg class name) + * Is the element from a constructor call with the given constructor name (e.g., class name) * * @param element the element - * @param constructorName the name of the constructor (eg class) - * @return true if its a constructor call from the given name, false otherwise + * @param constructorName the name of the constructor (e.g., class) + * @return true if it is a constructor call from the given name, false otherwise */ public boolean isElementFromConstructor(@NotNull PsiElement element, @NotNull String constructorName) { // java constructor @@ -292,7 +298,7 @@ public boolean isElementFromConstructor(@NotNull PsiElement element, @NotNull St } /** - * Is the given element from a Java method call with any of the given method names + * Is the given element from a Java method call with any of the given method names? * * @param element the psi element * @param methods method call names @@ -311,7 +317,7 @@ public boolean isFromJavaMethodCall(PsiElement element, boolean fromRouteBuilder * Returns the first parent of the given element which matches the given condition. * * @param element element from which the search starts - * @param strict if true, element itself cannot be returned if it matches the condition + * @param strict if true, the element itself cannot be returned if it matches the condition * @param matchCondition condition which the parent must match to be returned * @param stopCondition condition which stops the search, causing the method to return null */ @@ -358,7 +364,7 @@ public boolean isFromJavaMethod(PsiMethodCallExpression call, boolean fromRouteB } /** - * Is the given element from a XML tag with any of the given tag names + * Is the given element from an XML tag with any of the given tag names? * * @param xml the xml tag * @param methods xml tag names @@ -370,7 +376,7 @@ public boolean isFromXmlTag(@NotNull XmlTag xml, @NotNull String... methods) { } /** - * Is the given element from a XML tag with any of the given tag names + * Is the given element from an XML tag with any of the given tag names * * @param xml the xml tag * @param parentTag a special parent tag name to match first @@ -416,7 +422,7 @@ public boolean hasParentYAMLKeyValue(@NotNull YAMLKeyValue keyValue, @NotNull St } /** - * Is the given element from a XML tag with the parent and is of any of the given tag names + * Is the given element from an XML tag with the parent and is of any given tag names? * * @param xml the xml tag * @param parentTag a special parent tag name to match first @@ -440,8 +446,8 @@ public void iterateYamlFiles(Module module, Consumer yamlFileConsumer) fileIndex.iterateContent(f -> { if (yamlFiles.contains(f)) { PsiFile file = PsiManager.getInstance(module.getProject()).findFile(f); - if (file instanceof YAMLFile) { - yamlFileConsumer.accept((YAMLFile) file); + if (file instanceof YAMLFile yamlFile) { + yamlFileConsumer.accept(yamlFile); } } return true; @@ -456,8 +462,7 @@ public void iterateXmlDocumentRoots(Module module, Consumer rootTag) { fileIndex.iterateContent(f -> { if (xmlFiles.contains(f)) { PsiFile file = PsiManager.getInstance(module.getProject()).findFile(f); - if (file instanceof XmlFile) { - XmlFile xmlFile = (XmlFile) file; + if (file instanceof XmlFile xmlFile) { XmlTag root = xmlFile.getRootTag(); if (root != null) { rootTag.accept(xmlFile.getRootTag()); @@ -503,7 +508,6 @@ private int getCaretPositionInsidePsiElement(String stringLiteral) { return hackIndex; } - /** * Return the Query parameter at the cursor location for the query parameter. *
    @@ -513,7 +517,7 @@ private int getCaretPositionInsidePsiElement(String stringLiteral) { *
  • timer:trigger?repeatCount=0&delay=<cursor> will return {"delay",""}
  • *
  • jms:qu<cursor> will return {":qu", ""}
  • *
- * @return a list with the query parameter and the value if present. The query parameter is returned with separator char + * @return an array with the query parameter and the value if present. The query parameter is returned with separator char */ public String[] getQueryParameterAtCursorPosition(PsiElement element) { String positionText = extractTextFromElement(element); @@ -557,10 +561,7 @@ public boolean isCaretAtEndOfLine(PsiElement element) { public boolean isWhiteSpace(PsiElement element) { IElementType type = element.getNode().getElementType(); - if (type == TokenType.WHITE_SPACE) { - return true; - } - return false; + return type == TokenType.WHITE_SPACE; } public boolean isJavaDoc(PsiElement element) { @@ -645,19 +646,20 @@ public static YAMLKeyValue getYamlKeyValueAt(Project project, XSourcePosition po YAMLKeyValue keyValue = null; PsiElement psiElement = XDebuggerUtil.getInstance().findContextElement(file, position.getOffset(), project, false); - //This must be indent element because the position is at the beginning of the line - if (psiElement instanceof LeafPsiElement && "indent".equals(((LeafPsiElement) psiElement).getElementType().toString())) { - psiElement = psiElement.getNextSibling(); //This must be sequence item + // This must be the indent element because the position is at the beginning of the line + if (psiElement instanceof LeafPsiElement leafPsiElement && "indent".equals(leafPsiElement.getElementType().toString())) { + psiElement = psiElement.getNextSibling(); //This must be the sequence item Collection keyValues = null; - if (psiElement instanceof YAMLSequence) { //This is the beginning of sequence, get first item - psiElement = ((YAMLSequence) psiElement).getItems().get(0); + if (psiElement instanceof YAMLSequence yamlSequence) { + // This is the beginning of the sequence; get the first item + psiElement = yamlSequence.getItems().get(0); keyValues = ((YAMLSequenceItem) psiElement).getKeysValues(); - } else if (psiElement instanceof YAMLSequenceItem) { - keyValues = ((YAMLSequenceItem) psiElement).getKeysValues(); - } else if (psiElement instanceof YAMLKeyValue) { - keyValue = (YAMLKeyValue) psiElement; - } else if (psiElement instanceof YAMLMapping) { - keyValues = ((YAMLMapping) psiElement).getKeyValues(); + } else if (psiElement instanceof YAMLSequenceItem yamlSequenceItem) { + keyValues = yamlSequenceItem.getKeysValues(); + } else if (psiElement instanceof YAMLKeyValue yamlKeyValue) { + keyValue = yamlKeyValue; + } else if (psiElement instanceof YAMLMapping yamlMapping) { + keyValues = yamlMapping.getKeyValues(); } if (keyValues != null && !keyValues.isEmpty()) { keyValue = keyValues.iterator().next(); @@ -666,4 +668,13 @@ public static YAMLKeyValue getYamlKeyValueAt(Project project, XSourcePosition po return keyValue; } + + public static PsiClass findRouteBuilderClass(PsiManager manager) { + return ROUTE_BUILDER_OR_EXPRESSION_CLASS_QUALIFIED_NAME + .stream() + .map(fqn -> ClassUtil.findPsiClass(manager, fqn)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } } diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/JavaClassUtils.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/JavaClassUtils.java index cae02673..fe71f3ea 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/JavaClassUtils.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/JavaClassUtils.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; @@ -56,6 +55,13 @@ public class JavaClassUtils implements Disposable { * The prefix of all the classes in the java lang package. */ private static final String JAVA_LANG_PACKAGE = "java.lang."; + private static final List BEAN_ANNOTATIONS = Arrays.asList( + "org.springframework.stereotype.Component", + "org.springframework.stereotype.Service", + "org.springframework.stereotype.Repository", + "javax.inject.Named", + "jakarta.inject.Named" + ); public static JavaClassUtils getService() { return ApplicationManager.getApplication().getService(JavaClassUtils.class); @@ -66,11 +72,12 @@ public static JavaClassUtils getService() { * to class name decapitalized */ public String getBeanName(PsiClass clazz) { - final String beanName = getBeanName(clazz, "org.springframework.stereotype.Component") - .orElseGet(() -> getBeanName(clazz, "org.springframework.stereotype.Service") - .orElseGet(() -> getBeanName(clazz, "org.springframework.stereotype.Repository") - .orElse(Introspector.decapitalize(clazz.getText())))); - return beanName; + return BEAN_ANNOTATIONS + .stream() + .map(annotation -> getBeanName(clazz, annotation)) + .flatMap(Optional::stream) + .findFirst() + .orElseGet(() -> Introspector.decapitalize(clazz.getText())); } /** @@ -97,10 +104,9 @@ public Optional findBeanClassByName(String beanName, String annotation if (beanName.equals(StringUtils.stripDoubleQuotes(value))) { return Optional.of(psiClass); } - } } else { - if (Introspector.decapitalize(psiClass.getName()).equalsIgnoreCase(StringUtils.stripDoubleQuotes(beanName))) { + if (StringUtils.stripDoubleQuotes(beanName).equalsIgnoreCase(Introspector.decapitalize(psiClass.getName()))) { return Optional.of(psiClass); } } @@ -127,9 +133,9 @@ private Collection getClassesAnnotatedWith(Project project, String ann public JavaClassReference findClassReference(@NotNull PsiElement element) { List references = Arrays.stream(element.getReferences()) - .filter(r -> r instanceof JavaClassReference) - .map(r -> (JavaClassReference) r) - .collect(Collectors.toList()); + .filter(JavaClassReference.class::isInstance) + .map(JavaClassReference.class::cast) + .toList(); if (!references.isEmpty()) { return references.get(references.size() - 1); } @@ -139,8 +145,8 @@ public JavaClassReference findClassReference(@NotNull PsiElement element) { public PsiClass resolveClassReference(@NotNull PsiReference reference) { final PsiElement resolveElement = reference.resolve(); - if (resolveElement instanceof PsiClass) { - return (PsiClass) resolveElement; + if (resolveElement instanceof PsiClass psiClass) { + return psiClass; } else if (resolveElement instanceof PsiField) { final PsiType psiType = PsiUtil.getTypeByPsiElement(resolveElement); if (psiType != null) { @@ -180,24 +186,12 @@ public String toSimpleType(@Nullable String type) { if (result.startsWith(JAVA_LANG_PACKAGE)) { result = result.substring(JAVA_LANG_PACKAGE.length()); } - switch (result) { - case "string": - case "long": - case "boolean": - case "double": - case "float": - case "short": - case "char": - case "byte": - case "int": - return result; - case "character": - return "char"; - case "integer": - return "int"; - default: - return type; - } + return switch (result) { + case "string", "long", "boolean", "double", "float", "short", "char", "byte", "int" -> result; + case "character" -> "char"; + case "integer" -> "int"; + default -> type; + }; } /** diff --git a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/YamlPatternConditions.java b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/YamlPatternConditions.java index 03ac22b6..6fa800f4 100644 --- a/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/YamlPatternConditions.java +++ b/camel-idea-plugin/src/main/java/com/github/cameltooling/idea/util/YamlPatternConditions.java @@ -25,6 +25,7 @@ import com.intellij.psi.tree.IElementType; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; +import org.jetbrains.yaml.psi.YAMLKeyValue; /** * A utility call to define special pattern conditions used to build element pattern for Yaml content. @@ -69,6 +70,15 @@ public static PatternCondition withFirstChild(@NotNull return new FirstChildPatternCondition<>(pattern); } + /** + * @param key the key to validate the key value pair. + * @param value the value to validate the key value pair. + * @return a {@code PatternCondition} that accepts {@code YAMLKeyValue}s which match with the given key value pair. + */ + public static PatternCondition withPair(@NotNull String key, @NotNull String value) { + return new PairPatternCondition<>(key, value); + } + /** * @param pattern the pattern to validate against the last child. * @return a {@code PatternCondition} that accepts elements whose last child matches with the given pattern. @@ -112,6 +122,38 @@ public boolean accepts(@NotNull T t, ProcessingContext context) { } } + /** + * {@code PairPatternCondition} is a {@link PatternCondition} allowing to identify {@code YAMLKeyValue}s which + * match with the specific key value pair. + */ + private static class PairPatternCondition extends PatternCondition { + + /** + * The key to validate the key value pair. + */ + private final String key; + /** + * The value to validate the key value pair. + */ + private final String value; + + /** + * Construct a {@code PairPatternCondition} with the given key value pair. + * @param key the key to validate the key value pair. + * @param value the value to validate the key value pair. + */ + PairPatternCondition(@NotNull String key, @NotNull String value) { + super("withPair"); + this.key = key; + this.value = value; + } + + @Override + public boolean accepts(@NotNull T t, ProcessingContext context) { + return key.equals(t.getKeyText()) && value.equals(t.getValueText()); + } + } + /** * {@code LastChildPatternCondition} is a {@link PatternCondition} allowing to identify elements whose * last child matches with a specific pattern. diff --git a/camel-idea-plugin/src/main/resources/META-INF/plugin.xml b/camel-idea-plugin/src/main/resources/META-INF/plugin.xml index c6e0a10a..b337ff95 100644 --- a/camel-idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/camel-idea-plugin/src/main/resources/META-INF/plugin.xml @@ -1,15 +1,15 @@ org.apache.camel Apache Camel - 1.1.1 + 1.1.5 Apache Camel
  • Bug fixes
  • @@ -36,7 +36,7 @@ org.intellij.intelliLang - + @@ -79,8 +79,6 @@ - - diff --git a/camel-idea-plugin/src/main/resources/META-INF/pluginIcon.svg b/camel-idea-plugin/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 00000000..5909e08d --- /dev/null +++ b/camel-idea-plugin/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletNameCompletionIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletNameCompletionIT.java new file mode 100644 index 00000000..6e70b5b0 --- /dev/null +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletNameCompletionIT.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion; + +import java.util.Arrays; +import java.util.List; + +import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; + +/** + * Testing basic completion with Kamelet name in a Camel Kamelet binding. + */ +public class CamelKameletNameCompletionIT extends CamelLightCodeInsightFixtureTestCaseIT { + + @Override + protected String getTestDataPath() { + return "src/test/resources/testData/completion/kamelet/name"; + } + + public void testBindingNoKamelet() { + myFixture.configureByFiles("binding-no-kamelet.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingNoName() { + myFixture.configureByFiles("binding-no-name.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingUnknown() { + myFixture.configureByFiles("binding-unknown.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingBySource() { + myFixture.configureByFiles("binding-source.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("timer-source", "aws-s3-source"))); + assertFalse(strings.containsAll(Arrays.asList("log-sink", "avro-deserialize-action"))); + myFixture.type("ti"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("timer-source")); + assertFalse(strings.containsAll(Arrays.asList("log-sink", "avro-deserialize-action", "aws-s3-source"))); + } + + public void testBindingBySink() { + myFixture.configureByFiles("binding-sink.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("log-sink", "avro-deserialize-action"))); + assertFalse(strings.containsAll(Arrays.asList("timer-source", "aws-s3-source"))); + myFixture.type("av"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("avro-deserialize-action")); + assertFalse(strings.containsAll(Arrays.asList("log-sink", "timer-source", "aws-s3-source"))); + } + + public void testBindingBySteps() { + myFixture.configureByFiles("binding-steps.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("log-sink", "avro-deserialize-action"))); + assertFalse(strings.containsAll(Arrays.asList("timer-source", "aws-s3-source"))); + myFixture.type("av"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("avro-deserialize-action")); + assertFalse(strings.containsAll(Arrays.asList("log-sink", "timer-source", "aws-s3-source"))); + } + + public void testBinding() { + myFixture.configureByFiles("binding.yaml"); + myFixture.completeBasic(); + myFixture.type('\n'); + myFixture.checkResultByFile("bindingResult.yaml"); + } +} diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletOptionNameCompletionIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletOptionNameCompletionIT.java new file mode 100644 index 00000000..5138640b --- /dev/null +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletOptionNameCompletionIT.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion; + +import java.util.Arrays; +import java.util.List; + +import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; +import com.github.cameltooling.idea.service.CamelProjectPreferenceService; + +/** + * Testing basic completion with Kamelet option name in a Camel Kamelet binding. + */ +public class CamelKameletOptionNameCompletionIT extends CamelLightCodeInsightFixtureTestCaseIT { + + protected void tearDown() throws Exception { + try { + CamelProjectPreferenceService.getService(getProject()).setOnlyShowKameletOptions(true); + } finally { + super.tearDown(); + } + } + + @Override + protected String getTestDataPath() { + return "src/test/resources/testData/completion/kamelet/optionname"; + } + + public void testBindingNoKamelet() { + myFixture.configureByFiles("binding-no-kamelet.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingNoName() { + myFixture.configureByFiles("binding-no-name.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingUnknown() { + myFixture.configureByFiles("binding-unknown.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingBySource() { + myFixture.configureByFiles("binding-source.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("period", "message"))); + myFixture.type("pe"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("period")); + assertFalse(strings.contains("message")); + } + + public void testBindingBySink() { + myFixture.configureByFiles("binding-sink.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("loggerName", "level"))); + myFixture.type("lo"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("loggerName")); + assertFalse(strings.contains("level")); + } + + public void testBindingBySteps() { + myFixture.configureByFiles("binding-steps.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("field", "headerOutput"))); + myFixture.type("f"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("field")); + assertFalse(strings.contains("headerOutput")); + } + + public void testBinding() { + myFixture.configureByFiles("binding.yaml"); + myFixture.completeBasic(); + myFixture.type('\n'); + myFixture.checkResultByFile("bindingResult.yaml", true); + } +} diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletOptionValueCompletionIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletOptionValueCompletionIT.java new file mode 100644 index 00000000..6e4101e3 --- /dev/null +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/completion/CamelKameletOptionValueCompletionIT.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 com.github.cameltooling.idea.completion; + +import java.util.Arrays; +import java.util.List; + +import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; +import com.github.cameltooling.idea.service.CamelProjectPreferenceService; + +/** + * Testing basic completion with Kamelet option value in a Camel Kamelet binding. + */ +public class CamelKameletOptionValueCompletionIT extends CamelLightCodeInsightFixtureTestCaseIT { + + protected void tearDown() throws Exception { + try { + CamelProjectPreferenceService.getService(getProject()).setOnlyShowKameletOptions(true); + } finally { + super.tearDown(); + } + } + + @Override + protected String getTestDataPath() { + return "src/test/resources/testData/completion/kamelet/optionvalue"; + } + + public void testBindingNoKamelet() { + myFixture.configureByFiles("binding-no-kamelet.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingNoName() { + myFixture.configureByFiles("binding-no-name.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingUnknown() { + myFixture.configureByFiles("binding-unknown.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.isEmpty()); + } + + public void testBindingBySource() { + myFixture.configureByFiles("binding-source.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("text/plain")); + } + + public void testBindingBySink() { + myFixture.configureByFiles("binding-sink.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("INFO")); + } + + public void testBindingBySteps() { + myFixture.configureByFiles("binding-steps.yaml"); + myFixture.completeBasic(); + List strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.containsAll(Arrays.asList("true", "false"))); + myFixture.type("f"); + strings = myFixture.getLookupElementStrings(); + assertNotNull(strings); + assertTrue(strings.contains("false")); + assertFalse(strings.contains("true")); + } + + public void testBinding() { + myFixture.configureByFiles("binding.yaml"); + myFixture.completeBasic(); + myFixture.type('\n'); + myFixture.checkResultByFile("bindingResult.yaml"); + } +} diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/formatter/CamelPostFormatProcessorIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/formatter/CamelPostFormatProcessorIT.java index 01912529..d9010cb7 100644 --- a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/formatter/CamelPostFormatProcessorIT.java +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/formatter/CamelPostFormatProcessorIT.java @@ -72,6 +72,14 @@ public void testFormatting879() { doTest("Formatting879", null); } + /** + * Ensures that the test case defined in #945 + * is properly fixed. + */ + public void testFormatting945() { + doTest("Formatting945", null); + } + /** * Ensures that a partial format not including a route has no effect. */ diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithKarafArtifactTestIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithKarafArtifactTestIT.java index 9aaf132b..23661842 100644 --- a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithKarafArtifactTestIT.java +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithKarafArtifactTestIT.java @@ -18,6 +18,7 @@ import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; import com.github.cameltooling.idea.catalog.CamelCatalogProvider; +import com.github.cameltooling.idea.maven.CamelMavenVersionManager; import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; import org.jetbrains.annotations.Nullable; @@ -52,7 +53,7 @@ public void testGetCatalog() { * Ensure that the runtime provider can be loaded. */ public void testRuntimeProvider() { - getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager()); + getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager(getProject())); assertTrue(provider.loadRuntimeProviderVersion(getProject())); } } diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithQuarkusArtifactTestIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithQuarkusArtifactTestIT.java index 69ddc6b7..71e32e30 100644 --- a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithQuarkusArtifactTestIT.java +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithQuarkusArtifactTestIT.java @@ -18,6 +18,7 @@ import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; import com.github.cameltooling.idea.catalog.CamelCatalogProvider; +import com.github.cameltooling.idea.maven.CamelMavenVersionManager; import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; import org.jetbrains.annotations.Nullable; @@ -52,7 +53,7 @@ public void testGetCatalog() { * Ensure that the runtime provider can be loaded. */ public void testRuntimeProvider() { - getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager()); + getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager(getProject())); assertTrue(provider.loadRuntimeProviderVersion(getProject())); } } diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithSpringBootArtifactTestIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithSpringBootArtifactTestIT.java index c0e229d4..9556ee04 100644 --- a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithSpringBootArtifactTestIT.java +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithSpringBootArtifactTestIT.java @@ -18,6 +18,7 @@ import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; import com.github.cameltooling.idea.catalog.CamelCatalogProvider; +import com.github.cameltooling.idea.maven.CamelMavenVersionManager; import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; import org.jetbrains.annotations.Nullable; @@ -52,7 +53,7 @@ public void testGetCatalog() { * Ensure that the runtime provider can be loaded. */ public void testRuntimeProvider() { - getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager()); + getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager(getProject())); assertTrue(provider.loadRuntimeProviderVersion(getProject())); } } diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithoutRuntimeArtifactTestIT.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithoutRuntimeArtifactTestIT.java index 183ed7a7..9ea7bb88 100644 --- a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithoutRuntimeArtifactTestIT.java +++ b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/CamelCatalogProviderWithoutRuntimeArtifactTestIT.java @@ -18,6 +18,7 @@ import com.github.cameltooling.idea.CamelLightCodeInsightFixtureTestCaseIT; import com.github.cameltooling.idea.catalog.CamelCatalogProvider; +import com.github.cameltooling.idea.maven.CamelMavenVersionManager; import com.github.cameltooling.idea.util.ArtifactCoordinates; import com.intellij.testFramework.ServiceContainerUtil; import org.apache.camel.catalog.CamelCatalog; @@ -76,7 +77,7 @@ public void testSpringBootRuntimeProvider() { } private void testRuntimeProvider(CamelCatalogProvider provider) { - getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager()); + getProject().getService(CamelCatalogService.class).get().setVersionManager(new CamelMavenVersionManager(getProject())); assertTrue(provider.loadRuntimeProviderVersion(getProject())); } } diff --git a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/XmlUtilsTest.java b/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/XmlUtilsTest.java deleted file mode 100644 index 5a009b90..00000000 --- a/camel-idea-plugin/src/test/java/com/github/cameltooling/idea/service/XmlUtilsTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 com.github.cameltooling.idea.service; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -public class XmlUtilsTest { - - private Document document; - - public XmlUtilsTest() throws Exception { - document = XmlUtils.loadDocument(readTestXmlFile(), true); - } - - @Test - public void loadDocument() { - assertEquals("problems", document.getDocumentElement().getTagName()); - } - - @Test - public void getChildNodeByTagName() { - NodeList problem = document.getElementsByTagName("problem"); - Node item = problem.item(0); - Node description = XmlUtils.getChildNodeByTagName(item, "description"); - assertEquals("fileExist is not applicable in consumer only mode", description.getTextContent()); - - } - - @Test - public void returnNullWhenRootDoesNotContainChildren() throws Exception { - Document document = XmlUtils.loadDocument(readTestXmlFileWithoutChildren(), true); - Element root = document.getDocumentElement(); - Node nothing = XmlUtils.getChildNodeByTagName(document, "description"); - assertNull(nothing); - } - - private FileInputStream readTestXmlFile() { - FileInputStream fileInputStream = null; - try { - fileInputStream = new FileInputStream("src/test/resources/testData/inspectionxml/expected.xml"); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return fileInputStream; - } - - private FileInputStream readTestXmlFileWithoutChildren() { - FileInputStream fileInputStream = null; - try { - fileInputStream = new FileInputStream("src/test/resources/testData/inspectionxml/expected-xml-without-children.xml"); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return fileInputStream; - } -} diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-no-kamelet.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-no-kamelet.yaml new file mode 100644 index 00000000..b6aeff75 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-no-kamelet.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Foo + apiVersion: camel.apache.org/v1 + name: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-no-name.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-no-name.yaml new file mode 100644 index 00000000..36a50441 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-no-name.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + foo: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-sink.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-sink.yaml new file mode 100644 index 00000000..46304909 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-sink.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-source.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-source.yaml new file mode 100644 index 00000000..26a31cf4 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-source.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-steps.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-steps.yaml new file mode 100644 index 00000000..57dda848 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-steps.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + steps: + - ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-unknown.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-unknown.yaml new file mode 100644 index 00000000..16adf608 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding-unknown.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + foo: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding.yaml new file mode 100644 index 00000000..7d67cc92 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/binding.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: ti diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/bindingResult.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/bindingResult.yaml new file mode 100644 index 00000000..da1bef84 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/name/bindingResult.yaml @@ -0,0 +1,10 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-no-kamelet.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-no-kamelet.yaml new file mode 100644 index 00000000..44d70c97 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-no-kamelet.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Foo + apiVersion: camel.apache.org/v1 + name: log-sink + properties: + diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-no-name.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-no-name.yaml new file mode 100644 index 00000000..d4d2480a --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-no-name.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + foo: log-sink + properties: + diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-sink.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-sink.yaml new file mode 100644 index 00000000..73e32e94 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-sink.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + properties: + diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-source.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-source.yaml new file mode 100644 index 00000000..18699b84 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-source.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-steps.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-steps.yaml new file mode 100644 index 00000000..ebd6b417 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-steps.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + steps: + - ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: extract-field-action + properties: + diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-unknown.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-unknown.yaml new file mode 100644 index 00000000..2c85aa8e --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding-unknown.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + foo: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + properties: + diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding.yaml new file mode 100644 index 00000000..d7ae93e0 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/binding.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + pe diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/bindingResult.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/bindingResult.yaml new file mode 100644 index 00000000..ef3d95ff --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionname/bindingResult.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + period: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-no-kamelet.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-no-kamelet.yaml new file mode 100644 index 00000000..ade3b41b --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-no-kamelet.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Foo + apiVersion: camel.apache.org/v1 + name: log-sink + properties: + level: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-no-name.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-no-name.yaml new file mode 100644 index 00000000..55b58cfb --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-no-name.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + foo: log-sink + properties: + level: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-sink.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-sink.yaml new file mode 100644 index 00000000..d5ba1035 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-sink.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + properties: + level: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-source.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-source.yaml new file mode 100644 index 00000000..8748bdd7 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-source.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + contentType: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-steps.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-steps.yaml new file mode 100644 index 00000000..d25fee53 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-steps.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + steps: + - ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: extract-field-action + properties: + headerOutput: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-unknown.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-unknown.yaml new file mode 100644 index 00000000..682ed2b7 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding-unknown.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + foo: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + properties: + level: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding.yaml new file mode 100644 index 00000000..8748bdd7 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/binding.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + contentType: diff --git a/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/bindingResult.yaml b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/bindingResult.yaml new file mode 100644 index 00000000..42f60fd2 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/completion/kamelet/optionvalue/bindingResult.yaml @@ -0,0 +1,12 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: my-bind +spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + contentType: text/plain diff --git a/camel-idea-plugin/src/test/resources/testData/formatter/after/Formatting945.txt b/camel-idea-plugin/src/test/resources/testData/formatter/after/Formatting945.txt new file mode 100644 index 00000000..702644d8 --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/formatter/after/Formatting945.txt @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; + +public class Formatting945 extends RouteBuilder { + @Override + public void configure() { + from("direct:start") + .doTry() + .process(new ProcessorFail()) + .to("mock:result") + .doCatch(IOException.class, IllegalStateException.class) + .to("mock:catch") + .doFinally() + .to("mock:finally") + .end(); + + from("direct:start2") + .doTry() + .unmarshal(new JacksonDataFormat(objectMapper, Foo.class)) + .doCatch(JsonProcessingException.class) + .to("direct:deserialization-error-handler") + .end() + } + + public static class ProcessorFail implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + throw new IOException("Forced"); + } + } +} diff --git a/camel-idea-plugin/src/test/resources/testData/formatter/before/Formatting945.java b/camel-idea-plugin/src/test/resources/testData/formatter/before/Formatting945.java new file mode 100644 index 00000000..3cf5936a --- /dev/null +++ b/camel-idea-plugin/src/test/resources/testData/formatter/before/Formatting945.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; + +public class Formatting945 extends RouteBuilder { + @Override + public void configure() { + from("direct:start") + .doTry() + .process(new ProcessorFail()) + .to("mock:result") + .doCatch(IOException.class, IllegalStateException.class) + .to("mock:catch") + .doFinally() + .to("mock:finally") + .end(); + + from("direct:start2") + .doTry() + .unmarshal(new JacksonDataFormat(objectMapper, Foo.class)) + .doCatch(JsonProcessingException.class) + .to("direct:deserialization-error-handler") + .end() + } + + public static class ProcessorFail implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + throw new IOException("Forced"); + } + } +} diff --git a/gradle.properties b/gradle.properties index cf20c631..4c7b5346 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ -camelVersion = 4.0.1 +camelVersion = 4.1.0 camelQuarkusVersion = 3.4.0 camelKameletVersion = 4.0.1 -camelKarafVersion = 3.21.0 -ideaVersion=2023.2.2 -mavenResolverVersion=1.9.16 +camelKarafVersion = 3.21.1 +ideaVersion=2023.2.3 +mavenResolverVersion=1.9.14 \ No newline at end of file diff --git a/img/54-kamelet-binding-completion.gif b/img/54-kamelet-binding-completion.gif new file mode 100644 index 00000000..52921a5c Binary files /dev/null and b/img/54-kamelet-binding-completion.gif differ diff --git a/readme.md b/readme.md index 4fca6aaf..be67b2ed 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,7 @@ The plugin includes: - Code completion for Camel message headers (available `setHeader` and `header`) in Java, XML and YAML files (`ctrl + space`) with corresponding Quick documentation (`ctrl + j`) - Code completion for Camel property placeholders (cursor after `{{`) - Code completion for Camel options' key and value in properties and YAML files (`ctrl + space`) with corresponding Quick documentation (`ctrl + j`) +- Code completion for Kamelet name and options' key and value in Kamelet binding files (`ctrl + space`) with corresponding Quick documentation (`ctrl + j`) - Real time validation for Camel endpoints in Java, XML, YAML (underline errors in red) - Real time validation for Camel simple language in Java, XML, YAML (underline errors in red) - Endpoint options filtered to only include applicable options when used as consumer vs producer only mode @@ -98,7 +99,7 @@ Importing the project into IntelliJ as plug-in only require you choose ìmport f #### Running the plug-in with a previous versions of IDEA -The plugin is tested with `IDEA 2023.2.2` or newer, but if you want to try with an older version you can follow this guide +The plugin is tested with `IDEA 2023.2.3` or newer, but if you want to try with an older version you can follow this guide > - Follow the guide [build from source](#buildingfromsource) > - Change the attribute `` in `camel-idea-plugin/src/main/resources/META-INF/plugin.xml` to match the version. please see [document](http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html) for build number description