/*
* Copyright 2012 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* This file 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.amazonaws.eclipse.cloudformation.templates.editor;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Stack;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import com.amazonaws.eclipse.cloudformation.templates.TemplateArrayNode;
import com.amazonaws.eclipse.cloudformation.templates.TemplateObjectNode;
import com.amazonaws.eclipse.cloudformation.templates.TemplateValueNode;
import com.amazonaws.eclipse.cloudformation.templates.editor.TemplateEditor.TemplateDocument;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TemplateReconcilingStrategy implements IReconcilingStrategy, IReconcilingStrategyExtension {
private IDocument document;
private IProgressMonitor monitor;
private final ISourceViewer sourceViewer;
private static final ObjectMapper mapper = new ObjectMapper();
private static final JsonFactory factory = mapper.getJsonFactory();
private Stack<String> path = new Stack<String>();
private static final String ROOT_SCHEMA_OBJECT = "ROOT";
public TemplateReconcilingStrategy(ISourceViewer sourceViewer) {
this.sourceViewer = sourceViewer;
}
public void setDocument(IDocument document) {
this.document = document;
}
public void reconcile(IRegion partition) {
reconcile();
}
public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
reconcile();
}
private JsonLocation lastLocation;
private JsonLocation currentLocation;
/**
* Fetches the next token from the JsonParser. Also captures the location in
* the stream before and after fetching the token.
*
* @param parser
* The JsonParser objects from which the token has to be fetched.
* @return The JsonToken
*/
private JsonToken nextToken(JsonParser parser) throws IOException {
lastLocation = parser.getCurrentLocation();
JsonToken token = parser.nextToken();
currentLocation = parser.getCurrentLocation();
return token;
}
/**
* Returns the last location of the JsonParser.
*/
private JsonLocation getParserLastLocation(JsonParser parser) {
if (currentLocation != parser.getCurrentLocation()) {
getParserCurrentLocation(parser);
}
return lastLocation;
}
/**
* Returns the current location of the JsonParser.
*/
private JsonLocation getParserCurrentLocation(JsonParser parser) {
JsonLocation oldLocation = currentLocation;
currentLocation = parser.getCurrentLocation();
if (oldLocation != currentLocation) lastLocation = oldLocation;
return currentLocation;
}
/**
* Retrieves a JsonObject from the parser and creates a corresponding
* TemplateObjectNode. If the Json Object has an array or a child Json
* object, they are also parsed and associated with the TemplateObjectNode.
*
* @param parser
* The JsonParser from where the object has to be fetched.
* @return A TemplateObjectNode of the corresponding JsonObject.
*/
private TemplateObjectNode parseObject(JsonParser parser)
throws IOException {
JsonToken token = parser.getCurrentToken();
if (token != JsonToken.START_OBJECT)
throw new IllegalArgumentException(
"Current token not an object start token when attempting to parse an object: "
+ token);
TemplateObjectNode object = new TemplateObjectNode(
getParserCurrentLocation(parser));
do {
token = nextToken(parser);
if (token == JsonToken.END_OBJECT)
break;
if (token != JsonToken.FIELD_NAME)
throw new RuntimeException("Unexpected token: " + token);
String currentField = parser.getText();
push(currentField);
token = nextToken(parser);
if (token == JsonToken.START_OBJECT)
object.put(currentField, parseObject(parser));
if (token == JsonToken.START_ARRAY)
object.put(currentField, parseArray(parser));
if (token == JsonToken.VALUE_STRING)
object.put(currentField, parseValue(parser));
pop();
} while (true);
if (token != JsonToken.END_OBJECT)
throw new RuntimeException(
"Current token not an object end token: " + token);
object.setEndLocation(getParserCurrentLocation(parser));
return object;
}
/**
* Parses a given value string from the parser.
*
* @param parser
* The parser from where the value node has to be read.
* @return A TemplateValueNode object for the parsed value string.
*/
private TemplateValueNode parseValue(JsonParser parser) throws IOException,
JsonParseException {
TemplateValueNode node = new TemplateValueNode(parser.getText());
node.setStartLocation(getParserLastLocation(parser));
node.setEndLocation(getParserCurrentLocation(parser));
return node;
}
/**
* Parses a given array string from the parser.
*
* @param parser
* The parser from where the array has to be read.
* @return A TemplateArrayNode object for the parsed array.
*/
private TemplateArrayNode parseArray(JsonParser parser) throws IOException {
JsonToken token = parser.getCurrentToken();
if (token != JsonToken.START_ARRAY)
throw new IllegalArgumentException(
"Current token not an array start token when attempting to parse an array: "
+ token);
TemplateArrayNode array = new TemplateArrayNode(
getParserCurrentLocation(parser));
do {
token = nextToken(parser);
if (token == JsonToken.END_ARRAY)
break;
if (token == JsonToken.START_OBJECT)
array.add(parseObject(parser));
if (token == JsonToken.START_ARRAY)
array.add(parseArray(parser));
if (token == JsonToken.VALUE_STRING)
array.add(parseValue(parser));
} while (true);
if (token != JsonToken.END_ARRAY)
throw new RuntimeException("Current token not an array end token: "
+ token);
array.setEndLocation(getParserCurrentLocation(parser));
return array;
}
/**
* Parses the Json Object from the given JsonParser and returns the root node.
*
* @param parser The parser from where the array has to be read.
* @return The root node of the Json Object.
*/
private TemplateObjectNode parse(JsonParser parser) throws IOException {
nextToken(parser);
push(ROOT_SCHEMA_OBJECT);
TemplateObjectNode rootNode = parseObject(parser);
pop();
return rootNode;
}
/**
* Pushes the given token to the stack.
* @param token
*/
private void push(String token){
path.push(token);
}
/**
* Pops a token from the stack.
* @return
*/
private String pop(){
return path.pop();
}
/**
* Reconciles the Json document extracted from the Json Editor.
*/
private void reconcile() {
TemplateDocument templateDocument = (TemplateDocument) this.document;
try {
path.clear();
JsonParser parser = factory.createJsonParser(document.get());
TemplateObjectNode model = parse(parser);
templateDocument.setModel(model);
removeAllAnnotations();
} catch (JsonParseException e) {
templateDocument.setPath(Collections.unmodifiableList(path));
IAnnotationModel annotationModel = sourceViewer.getAnnotationModel();
if (annotationModel != null) {
Annotation annotation = new Annotation("org.eclipse.ui.workbench.texteditor.error", true, e.getMessage());
annotationModel.addAnnotation(annotation, new Position((int)e.getLocation().getCharOffset(), 10));
} else {
throw new RuntimeException("No AnnotationModel configured");
}
} catch (Exception e) {
// TODO: Add a status annotation for this
}
if (monitor != null) monitor.done();
}
/** Clears all annotations from the annotation model. */
private void removeAllAnnotations() {
IAnnotationModel annotationModel = sourceViewer.getAnnotationModel();
if (annotationModel == null) return;
Iterator<?> annotationIterator = annotationModel.getAnnotationIterator();
while (annotationIterator.hasNext()) {
Annotation annotation = (Annotation)annotationIterator.next();
annotationModel.removeAnnotation(annotation);
}
}
public void setProgressMonitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
public void initialReconcile() {
reconcile();
}
}