/******************************************************************************* * Copyright (c) 2016 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.cloudfoundry.deployment; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModelExtension; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.ApplicationManifestHandler; import org.springframework.ide.eclipse.boot.util.Log; import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlASTProvider; import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlFileAST; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.ScalarNode; import org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.parser.ParserException; import org.yaml.snakeyaml.scanner.ScannerException; /** * Reconciler responsible for creating annotation at application names positions * in the Deployment YAML document * * @author Alex Boyko * */ public class AppNameReconciler { /** * YAML parser */ private YamlASTProvider fParser; /** * Constant application name. If not <code>null</code> then corresponding annotation must be selected */ final private String fAppName; public AppNameReconciler(YamlASTProvider parser, String appName) { fParser = parser; fAppName = appName; } /** * Re-populates annotation model with app name annotations based on document contents * * @param document The YAML document * @param annotationModel Application Names annotation model * @param monitor Progress monitor */ public void reconcile(IDocument document, AppNameAnnotationModel annotationModel, IProgressMonitor monitor) { if (annotationModel == null) { return; } List<Annotation> toRemove= new ArrayList<>(); Iterator<? extends Annotation> iter= annotationModel.getAnnotationIterator(); while (iter.hasNext()) { Annotation annotation= iter.next(); if (AppNameAnnotation.TYPE.equals(annotation.getType())) { toRemove.add(annotation); } } Annotation[] annotationsToRemove= toRemove.toArray(new Annotation[toRemove.size()]); /* * Create brand new annotation to position map based on docs contents */ Map<AppNameAnnotation, Position> annotationsToAdd = createAnnotations(document, annotationModel, monitor); /* * Update annotation model */ if (annotationModel instanceof IAnnotationModelExtension) ((IAnnotationModelExtension)annotationModel).replaceAnnotations(annotationsToRemove, annotationsToAdd); else { for (int i= 0; i < annotationsToRemove.length; i++) annotationModel.removeAnnotation(annotationsToRemove[i]); for (iter= annotationsToAdd.keySet().iterator(); iter.hasNext();) { Annotation annotation= iter.next(); annotationModel.addAnnotation(annotation, annotationsToAdd.get(annotation)); } } } /** * Create new annotation to position mapping based on the document contents * * @param annotationModel Application name annotations model * @return Map of annotations to their corresponding positions */ private Map<AppNameAnnotation, Position> createAnnotations(IDocument document, AppNameAnnotationModel annotationModel, IProgressMonitor monitor) { Map<AppNameAnnotation, Position> annotationsMap = new LinkedHashMap<>(); monitor.beginTask("Calculating application names", 100); try { YamlFileAST ast = fParser.getAST(document); String contents = document.get(); List<Node> rootList = ast.getNodes(); monitor.worked(70); if (rootList.size() == 1) { Node root = rootList.get(0); SequenceNode applicationsNode = YamlGraphDeploymentProperties.getNode(root, ApplicationManifestHandler.APPLICATIONS_PROP, SequenceNode.class); if (applicationsNode == null) { /* * No 'applications' YAML node consider root elements to the deployment properties of an application */ ScalarNode node = YamlGraphDeploymentProperties.getPropertyValue(root, ApplicationManifestHandler.NAME_PROP, ScalarNode.class); if (node != null) { /* * There is 'name' property present, so yes root has application deployment props */ annotationsMap.put(new AppNameAnnotation(node.getValue(), true), new Position(root.getStartMark().getIndex(), getLastWhiteCharIndex(contents, root.getEndMark().getIndex()) - root.getStartMark().getIndex())); } } else { /* * Go through entries in the 'applications' sequence node */ for (Node appNode : applicationsNode.getValue()) { ScalarNode node = YamlGraphDeploymentProperties.getNode(appNode, ApplicationManifestHandler.NAME_PROP, ScalarNode.class); if (node != null) { /* * Add application name annotation entry */ annotationsMap.put(new AppNameAnnotation(node.getValue()), new Position(appNode.getStartMark().getIndex(), getLastWhiteCharIndex(contents, appNode.getEndMark().getIndex()) - appNode.getStartMark().getIndex())); } } } monitor.worked(20); if (!annotationsMap.isEmpty()) { if (fAppName == null) { /* * Select either previously selected app name annotation or the first found */ reselectAnnotation(annotationModel, annotationsMap); } else { /* * Select annotation corresponding to application name == to fAppName */ selectAnnotationByAppName(annotationsMap); } monitor.worked(10); } } } catch (ParserException | ScannerException e) { // Ignore these exceptions as they'd appear as syntax errors in the editor } catch (Throwable t) { Log.log(t); } finally { monitor.done(); } return annotationsMap; } /** * Selects annotation from the map corresponding to currently selected * annotation. Otherwise just selects the first found annotation * * @param annotationModel Application name annotations model * @param annotationsMap Map of application name annotations to positions */ private void reselectAnnotation(AppNameAnnotationModel annotationModel, Map<AppNameAnnotation, Position> annotationsMap) { AppNameAnnotation selected = annotationModel.getSelectedAppAnnotation(); Map.Entry<AppNameAnnotation, Position> newSelected = null; if (selected != null) { Position selectedPosition = annotationModel.getPosition(selected); for (Map.Entry<AppNameAnnotation, Position> entry : annotationsMap.entrySet()) { /* * Check if application name matches */ if (entry.getKey().getText().equals(selected.getText())) { /* * If name matches see if previous match is further away * from previously selected annotation offset than the * current match. Update the match accordingly. */ if (newSelected == null) { newSelected = entry; } else if (Math.abs(newSelected.getValue().getOffset() - selectedPosition.getOffset()) > Math.abs(entry.getValue().getOffset() - selectedPosition.getOffset())){ newSelected = entry; } } else if (entry.getValue().getOffset() == selectedPosition.getOffset() && newSelected == null) { newSelected = entry; } } } if (newSelected == null) { /* * No matches found. Select the first annotation to have something selected. */ newSelected = annotationsMap.entrySet().iterator().next(); } newSelected.getKey().markSelected(); } /** * Select annotation matching constant application name, i.e. <code>FAppName</code> * * @param annotationsMap Map of application name annotations to positions */ private void selectAnnotationByAppName(Map<AppNameAnnotation, Position> annotationsMap) { for (Map.Entry<AppNameAnnotation, Position> entry : annotationsMap.entrySet()) { if (entry.getKey().getText().equals(fAppName)) { entry.getKey().markSelected(); return; } } } /** * Returns the first 'white' char after a word appearing before the passed index * * @param text Text * @param index Index to start looking from * @return The first 'white' char position in a string */ private static int getLastWhiteCharIndex(String text, int index) { if (index == text.length()) { return index; } int i = index; for (; i >= 0 && Character.isWhitespace(text.charAt(i)); i--) { // Nothing to do } // Special case: if non white char is at position 'index' then return value of 'index' return i == index ? i : i + 1; } }