blob: a30c5c246378a85f66ad0fdde3b3b43bd8819a45 [file] [log] [blame]
/*
* 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 org.netbeans.modules.micronaut;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import org.netbeans.api.editor.document.EditorDocumentUtils;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.csl.api.Error;
import org.netbeans.modules.csl.api.Hint;
import org.netbeans.modules.csl.api.HintsProvider;
import org.netbeans.modules.csl.api.Rule;
import org.netbeans.modules.csl.api.RuleContext;
import org.netbeans.modules.csl.api.Severity;
import org.netbeans.modules.csl.api.StructureItem;
import org.netbeans.modules.csl.api.StructureScanner;
import org.netbeans.modules.csl.core.Language;
import org.netbeans.modules.csl.core.LanguageRegistry;
import org.netbeans.modules.csl.spi.DefaultError;
import org.netbeans.modules.csl.spi.ParserResult;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
/**
*
* @author Dusan Balek
*/
public class MicronautConfigValidator implements HintsProvider {
@Override
public void computeHints(HintsManager manager, RuleContext context, List<Hint> hints) {
}
@Override
public void computeSuggestions(HintsManager manager, RuleContext context, List<Hint> suggestions, int caretOffset) {
}
@Override
public void computeSelectionHints(HintsManager manager, RuleContext context, List<Hint> suggestions, int start, int end) {
}
@Override
public void computeErrors(HintsManager manager, RuleContext context, List<Hint> hints, List<Error> unhandled) {
FileObject fo = EditorDocumentUtils.getFileObject(context.doc);
if (MicronautConfigUtilities.isMicronautConfigFile(fo)) {
final ParserResult parserResult = context.parserResult;
final List<? extends Error> diagnostics = parserResult.getDiagnostics();
if (diagnostics != null && !diagnostics.isEmpty()) {
unhandled.addAll(diagnostics);
}
final Project project = FileOwnerQuery.getOwner(fo);
if (project != null) {
if (MicronautConfigProperties.hasConfigMetadata(project)) {
final Language language = LanguageRegistry.getInstance().getLanguageByMimeType(parserResult.getSnapshot().getMimeType());
if (language != null) {
final StructureScanner scanner = language.getStructure();
if (scanner != null) {
final List<? extends StructureItem> structures = scanner.scan(parserResult);
if (!structures.isEmpty()) {
final Map<String, ConfigurationMetadataProperty> properties = MicronautConfigProperties.getProperties(project);
if (!properties.isEmpty()) {
validate(parserResult, structures, "", properties, unhandled);
}
}
}
}
}
}
}
}
@Override
public void cancel() {
}
@Override
public List<Rule> getBuiltinRules() {
return null;
}
@Override
public RuleContext createRuleContext() {
return new RuleContext();
}
@NbBundle.Messages({
"MSG_InvalidValue=Invalid value \"{0}\", must be of type \"{1}\"",
})
private static void validate(ParserResult parserResult, List<? extends StructureItem> structure, String name, Map<String, ConfigurationMetadataProperty> properties, List<Error> errors) {
final FileObject fileObject = parserResult.getSnapshot().getSource().getFileObject();
final CharSequence text = parserResult.getSnapshot().getText();
for (StructureItem item : structure) {
final String fullName = name.isEmpty() ? item.getName() : name + '.' + item.getName();
List<? extends StructureItem> nestedItems = item.getNestedItems();
if (nestedItems.isEmpty()) {
final ConfigurationMetadataProperty property = properties.get(fullName);
if (property != null) {
final String type = property.getType();
if (type != null && !"java.lang.String".equals(type)) {
try {
final String itemText = text.subSequence((int) item.getPosition(), (int) item.getEndPosition()).toString();
int idx = itemText.lastIndexOf(':');
if (idx >= 0) {
final String value = itemText.substring(idx + 1).trim();
if (!value.isEmpty()) {
int start = (int) item.getPosition() + itemText.indexOf(value, idx + 1);
int end = start + value.length();
switch (type) {
case "boolean":
if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) {
errors.add(DefaultError.createDefaultError(null, Bundle.MSG_InvalidValue(value, type), null, fileObject, start, end, false, Severity.ERROR));
}
break;
case "java.lang.Integer":
try {
Integer.parseInt(value);
} catch (NumberFormatException e) {
errors.add(DefaultError.createDefaultError(null, Bundle.MSG_InvalidValue(value, type), null, fileObject, start, end, false, Severity.ERROR));
}
break;
case "java.lang.Long":
try {
Long.parseLong(value);
} catch (NumberFormatException e) {
errors.add(DefaultError.createDefaultError(null, Bundle.MSG_InvalidValue(value, type), null, fileObject, start, end, false, Severity.ERROR));
}
break;
default:
JavaSource js = JavaSource.create(ClasspathInfo.create(fileObject));
if (js != null) {
try {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
TypeElement typeElement = cc.getElements().getTypeElement(type);
if (typeElement != null && typeElement.getKind() == ElementKind.ENUM) {
if (!typeElement.getEnclosedElements().stream().anyMatch(e -> e.getKind() == ElementKind.ENUM_CONSTANT && value.contentEquals(e.getSimpleName()))) {
errors.add(DefaultError.createDefaultError(null, Bundle.MSG_InvalidValue(value, type), null, fileObject, start, end, false, Severity.ERROR));
}
}
}, true);
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
}
}
}
}
}
} catch (IndexOutOfBoundsException e) {
}
}
}
} else {
validate(parserResult, nestedItems, fullName, properties, errors);
}
}
}
}