diff options
Diffstat (limited to 'json/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/validation/DelegatingSourceValidator.java')
-rw-r--r-- | json/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/validation/DelegatingSourceValidator.java | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/json/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/validation/DelegatingSourceValidator.java b/json/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/validation/DelegatingSourceValidator.java new file mode 100644 index 0000000000..46df80e9cb --- /dev/null +++ b/json/bundles/org.eclipse.wst.json.ui/src/org/eclipse/wst/json/ui/internal/validation/DelegatingSourceValidator.java @@ -0,0 +1,670 @@ +/******************************************************************************* + * Copyright (c) 2005, 2011 IBM Corporation and others. + * 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: + * IBM Corporation - initial API and implementation + * Angelo Zerr <angelo.zerr@gmail.com> - copied from org.eclipse.wst.xml.ui.internal.validation.DelegatingSourceValidator + * modified in order to process JSON Objects. + *******************************************************************************/ +package org.eclipse.wst.json.ui.internal.validation; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; +import org.eclipse.wst.json.core.document.IJSONDocument; +import org.eclipse.wst.json.core.document.IJSONModel; +import org.eclipse.wst.json.core.validation.AnnotationMsg; +import org.eclipse.wst.json.ui.internal.Logger; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.ui.internal.reconcile.validator.AnnotationInfo; +import org.eclipse.wst.sse.ui.internal.reconcile.validator.IncrementalReporter; +import org.eclipse.wst.validation.internal.core.IMessageAccess; +import org.eclipse.wst.validation.internal.core.ValidationException; +import org.eclipse.wst.validation.internal.provisional.core.IMessage; +import org.eclipse.wst.validation.internal.provisional.core.IProjectValidationContext; +import org.eclipse.wst.validation.internal.provisional.core.IReporter; +import org.eclipse.wst.validation.internal.provisional.core.IValidationContext; +import org.eclipse.wst.validation.internal.provisional.core.IValidator; +import org.eclipse.wst.validation.internal.provisional.core.IValidatorJob; + +/** + * A DelegatingReconcileValidator calls its delegate validator to get a list + * of validation error IMessages. Using information in this IMessage the + * DelegatingReconcileValidator updates the IMessage with an offset and length + * to give a good range to be "squiggled" and adds the messages to the + * IReporter + * + * @author Mark Hutchinson + * + */ +public abstract class DelegatingSourceValidator implements IValidator { + // the selection strategies: + protected static final String ALL_ATTRIBUTES = "ALL_ATTRIBUTES"; //$NON-NLS-1$ + protected static final String ATTRIBUTE_NAME = "ATTRIBUTE_NAME"; //$NON-NLS-1$ + protected static final String ATTRIBUTE_NAME_LAST = "ATTRIBUTE_NAME_LAST"; //$NON-NLS-1$ + protected static final String ATTRIBUTE_VALUE = "ATTRIBUTE_VALUE"; //$NON-NLS-1$ + protected static final String START_TAG = "START_TAG"; //$NON-NLS-1$ + protected static final String TEXT = "TEXT"; //$NON-NLS-1$ + protected static final String FIRST_NON_WHITESPACE_TEXT = "FIRST_NON_WHITESPACE_TEXT"; //$NON-NLS-1$ + protected static final String TEXT_ENTITY_REFERENCE = "TEXT_ENTITY_REFERENCE"; //$NON-NLS-1$ + protected static final String VALUE_OF_ATTRIBUTE_WITH_GIVEN_VALUE = "VALUE_OF_ATTRIBUTE_WITH_GIVEN_VALUE"; //$NON-NLS-1$ + protected static final String END_TAG = "END_TAG"; //$NON-NLS-1$ + private static final String QUICKASSISTPROCESSOR = IQuickAssistProcessor.class.getName(); + + + + /** + * This constant specifies the attribute name that specifies the side of + * the 'start tag' that the validator has used to report an error. A + * validator may choose to report a message at the left (the start of the + * start tag)or at the right (the end of the start tag). When this + * attribute is not specified error ERROR_SIDE_LEFT is the default. + */ + protected static final String ERROR_SIDE = "ERROR_SIDE"; //$NON-NLS-1$ + + /** + * When the ERROR_SIDE attribute specifies the ERROR_SIDE_LEFT value it is + * assumed that the message specifies a location to the left of the start + * tag + */ + protected static final String ERROR_SIDE_LEFT = "ERROR_SIDE_LEFT"; //$NON-NLS-1$ + + /** + * When the ERROR_SIDE attribute specifies the ERROR_SIDE_RIGHT value it + * is assumed that the message specifies a location to the right of the + * start tag + */ + protected static final String ERROR_SIDE_RIGHT = "ERROR_SIDE_RIGHT"; //$NON-NLS-1$ + + protected static final String COLUMN_NUMBER_ATTRIBUTE = "columnNumber"; //$NON-NLS-1$ + protected static final String SQUIGGLE_SELECTION_STRATEGY_ATTRIBUTE = "squiggleSelectionStrategy"; //$NON-NLS-1$ + protected static final String SQUIGGLE_NAME_OR_VALUE_ATTRIBUTE = "squiggleNameOrValue"; //$NON-NLS-1$ + + public DelegatingSourceValidator() { + super(); // constructor + } + + public void cleanup(IReporter arg0) { // don't need to implement + } + + // My Implementation of IHelper + class MyHelper implements IProjectValidationContext { + InputStream inputStream; + + IFile file; + + public MyHelper(InputStream inputStream, IFile file) { + this.inputStream = inputStream; + this.file = file; + } + + public int getBuildKind() { + return 0; + } + + public Object loadModel(String symbolicName, Object[] parms) { + if (symbolicName.equals("getFile")) { //$NON-NLS-1$ + return file; + } + return null; + } + + public Object loadModel(String symbolicName) { + if (symbolicName.equals("inputStream")) { //$NON-NLS-1$ + return inputStream; + } + return null; + } + + public String[] getURIs() { + if (file != null) { + return new String[]{file.getFullPath().toString()}; + } + return new String[0]; + } + + public IProject getProject() { + if (file != null) { + return file.getProject(); + } + return null; + } + } + + // My Implementation of IReporter + class MyReporter implements IReporter { + List list = new ArrayList(); + + public MyReporter() { + super(); + } + + public void addMessage(IValidator origin, IMessage message) { + list.add(message); + } + + public void displaySubtask(IValidator validator, IMessage message) { + /* do not need to implement */ + } + + public IMessageAccess getMessageAccess() { + return null; + } + + public boolean isCancelled() { + return false; + } + + public void removeAllMessages(IValidator origin, Object object) { // do + /* do not need to implement */ + } + + public void removeAllMessages(IValidator origin) { + /* do not need to implement */ + } + + public void removeMessageSubset(IValidator validator, Object obj, String groupName) {// do + /* do not need to implement */ + } + + public List getMessages() { + return list; + } + } + + protected abstract IValidator getDelegateValidator(); + + /** + * Calls a delegate validator getting and updates it's list of + * ValidationMessages with a good squiggle offset and length. + * + * @param helper + * loads an object. + * @param reporter + * Is an instance of an IReporter interface, which is used for + * interaction with the user. + */ + public void validate(IValidationContext helper, IReporter reporter) throws ValidationException { + String[] delta = helper.getURIs(); + if (delta.length > 0) { + // get the file, model and document: + IFile file = getFile(delta[0]); + IJSONModel jsonModel = null; + if (file != null) + jsonModel = getModelForResource(file); + // some problem occurred, abort + if (jsonModel == null) + return; + + try { + IJSONDocument document = jsonModel.getDocument(); + + // store the text in a byte array; make a full copy to ease + // any threading problems + byte[] byteArray; + try { + byteArray = jsonModel.getStructuredDocument().get().getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) { + // Not likely to happen + byteArray = jsonModel.getStructuredDocument().get().getBytes(); + } + + if (isDelegateValidatorEnabled(file)) { + IValidator validator = getDelegateValidator(); + if (validator != null) { + // Validate the file: + IValidationContext vHelper = new MyHelper(new ByteArrayInputStream(byteArray), file); + MyReporter vReporter = new MyReporter(); + if (validator instanceof IValidatorJob) { + ((IValidatorJob) validator).validateInJob(vHelper, vReporter); + } + else { + validator.validate(vHelper, vReporter); + } + List messages = vReporter.list; + + // set the offset and length + updateValidationMessages(messages, document, reporter); + } + } + } + + + finally { + if (jsonModel != null) { + jsonModel.releaseFromRead(); + } + } + } + } + + /** + * iterates through the messages and calculates a "better" offset and + * length + * + * @param messages - + * a List of IMessages + * @param document - + * the document + * @param reporter - + * the reporter the messages are to be added to + */ + protected void updateValidationMessages(List messages, IJSONDocument document, IReporter reporter) { + for (int i = 0; i < messages.size(); i++) { + IMessage message = (IMessage) messages.get(i); + try { + if (message.getAttribute(COLUMN_NUMBER_ATTRIBUTE) != null) { + int column = ((Integer) message.getAttribute(COLUMN_NUMBER_ATTRIBUTE)).intValue(); + if (message.getAttribute(AnnotationMsg.PROBMLEM_ID) != null && reporter instanceof IncrementalReporter){ + Integer problemId = (Integer)message.getAttribute(AnnotationMsg.PROBMLEM_ID); + SyntaxQuickAssistProcessor processor = new SyntaxQuickAssistProcessor(); + processor.setProblemId(problemId.intValue()); + message.setOffset(column); + Integer length = (Integer) message.getAttribute(AnnotationMsg.LENGTH); + message.setLength( length.intValue()); + Object attrValue = message.getAttribute(AnnotationMsg.ATTRVALUETEXT); + if (attrValue != null) + processor.setAdditionalFixInfo(attrValue); + else{ + Object attrValueNo = message.getAttribute(AnnotationMsg.ATTRVALUENO); + if (attrValueNo != null){ + int len = ((Integer)attrValueNo).intValue(); + Object[] objArray = new Object[len]; + for ( int j=0; j<len; j++){ + objArray[j] = message.getAttribute(AnnotationMsg.ATTRNO + j); + } + processor.setAdditionalFixInfo(objArray); + } + + } + message.setAttribute(QUICKASSISTPROCESSOR, processor); + AnnotationInfo info = new AnnotationInfo(message); + ((IncrementalReporter) reporter).addAnnotationInfo(this, info); + } + else { + String selectionStrategy = (String) message.getAttribute(SQUIGGLE_SELECTION_STRATEGY_ATTRIBUTE); + String nameOrValue = (String) message.getAttribute(SQUIGGLE_NAME_OR_VALUE_ATTRIBUTE); + + // convert the line and Column numbers to an offset: + int start = document.getStructuredDocument().getLineOffset(message.getLineNumber() - 1) + column - 1; + int[] result = computeStartAndEndLocation(start, selectionStrategy, getErrorSide(message), nameOrValue, document); + if (result != null) { + message.setOffset(result[0]); + message.setLength(result[1] - result[0]); + + reporter.addMessage(this, message); + } + } + } + + } + catch (BadLocationException e) { // this exception should not + // occur - it is thrown if + // trying to convert an + // invalid line number to and + // offset + } + + } + } + + /** + * @param delta + * the IFileDelta containing the file name to get + * @return the IFile + */ + public IFile getFile(String delta) { + IPath path = new Path(delta); + if (path.segmentCount() > 1) { + IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path); + if (file != null && file.exists()) + return file; + } + return null; + } + + /** + * + * @param file + * the file to get the model for + * @return the file's JSONModel or null + */ + protected IJSONModel getModelForResource(IFile file) { + IStructuredModel model = null; + IModelManager manager = StructuredModelManager.getModelManager(); + + try { + model = manager.getModelForRead(file); + // TODO.. HTML validator tries again to get a model a 2nd way + } + catch (Exception e) { + Logger.log(Logger.ERROR_DEBUG, file.getFullPath().toString(), e); + } + + if (model instanceof IJSONModel) + return (IJSONModel) model; + if (model != null) + model.releaseFromRead(); + return null; + } + + /** + * @deprecated use computeStartEndLocation(int startOffset, String + * errorMessage, String selectionStrategy, boolean leftError, + * String nameOrValue, IJSONDocument document) { + * + */ + protected int[] computeStartEndLocation(int startOffset, String errorMessage, String selectionStrategy, String nameOrValue, IJSONDocument document) { + return computeStartAndEndLocation(startOffset, selectionStrategy, ERROR_SIDE_RIGHT, nameOrValue, document); + } + + /** + * Calculates the "better" offsets. + * + * @param startOffset - + * the offset given by Xerces + * @param errorMessage - + * the Xerces error Message + * @param selectionStrategy - + * the selectionStrategy + * @param document - + * the document + * @return int[] - position 0 has the start offset of the squiggle range, + * position 1 has the endOffset + */ + /* + * The way the offsets is calculated is: - find the indexed region + * (element) closest to the given offset - if we are between two elements, + * choosing left or right element will depend on parameter 'errorSide' - + * based on the selectionStrategy choose the underlining strategy (eg + * START_TAG means underline the start tag of that element) - use + * information from nameOrValue and the JSON to get better offsets + * + */ + protected int[] computeStartAndEndLocation(int startOffset, String selectionStrategy, String errorSide, String nameOrValue, IJSONDocument document) { + try { + int startEndPositions[] = new int[2]; + + IndexedRegion region = document.getModel().getIndexedRegion(startOffset); + IndexedRegion prevRegion = document.getModel().getIndexedRegion(startOffset - 1); + + if (prevRegion != region) { + // if between two regions we use the 'errorSide' to understand + // which + // element is applicable. if we know the error has been + // reported to the + // right of the tag, then we can assume we need to step back + // to the previous + // region to land at the 'correct location. Otherwise assume + // we're + // exactly where we need to be. + if (ERROR_SIDE_LEFT.equals(errorSide)) { + region = prevRegion; + } + } + + // initialize start and end positions to be the start positions + // this means if the + // special case is not taken care of below the start and end + // offset are set to be + // the start of the region where the error was +// if (region != null) { +// startEndPositions[0] = region.getStartOffset(); +// startEndPositions[1] = startEndPositions[0]; +// } +// else { // this will message will not get added to the IReporter +// // since the length is 0 +// startEndPositions[0] = 0; +// startEndPositions[1] = 0; +// } +// if (region instanceof Node) { +// Node node = (Node) region; +// +// if (START_TAG.equals(selectionStrategy)) {// then we want to +// // underline the opening tag +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// IJSONElement element = (IJSONElement) node; +// startEndPositions[0] = element.getStartOffset() + 1; +// startEndPositions[1] = startEndPositions[0] + element.getTagName().length(); +// } +// } +// else if (END_TAG.equals(selectionStrategy)) {// then we want to +// // underline the end tag +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// IJSONElement element = (IJSONElement) node; +// startEndPositions[0] = element.getEndStartOffset(); +// startEndPositions[1] = element.getEndOffset(); +// } +// } +// else if (ATTRIBUTE_NAME.equals(selectionStrategy)) { // in +// // underline the attribute's name +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// IJSONElement element = (IJSONElement) node; +// IJSONNode attributeNode = (IJSONNode) (element.getAttributeNode(nameOrValue)); +// if (attributeNode != null) { +// startEndPositions[0] = attributeNode.getStartOffset(); +// startEndPositions[1] = attributeNode.getStartOffset() + nameOrValue.length(); +// } +// } +// } +// else if (ATTRIBUTE_VALUE.equals(selectionStrategy)) { +// // underline the attribute's value +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// IJSONElement element = (IJSONElement) node; +// IJSONAttr attributeNode = (IJSONAttr) (element.getAttributeNode(nameOrValue)); +// if (attributeNode != null) { +// startEndPositions[0] = attributeNode.getValueRegionStartOffset(); +// String valueRegionText = attributeNode.getValueRegionText(); +// int valueRegionLength = valueRegionText == null ? 0 : valueRegionText.length(); +// startEndPositions[1] = startEndPositions[0] + valueRegionLength; +// } +// } +// } +// else if (ALL_ATTRIBUTES.equals(selectionStrategy)) { +// // underline all attributes +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// IJSONElement element = (IJSONElement) node; +// NamedNodeMap attributes = element.getAttributes(); +// if (attributes != null) { +// IJSONNode first = (IJSONNode) attributes.item(0); +// IJSONNode last = (IJSONNode) attributes.item(attributes.getLength() - 1); +// if ((first != null) && (last != null)) { +// startEndPositions[0] = first.getStartOffset(); +// startEndPositions[1] = last.getEndOffset(); +// } +// } +// } +// } +// else if (TEXT.equals(selectionStrategy)) { +// // underline the text between the tags +// if (node.getNodeType() == Node.TEXT_NODE) { +// IJSONText textNode = (IJSONText) node; +// int start = textNode.getStartOffset(); +// String value = textNode.getNodeValue(); +// int index = 0; +// char curChar = value.charAt(index); +// // here we are finding start offset by skipping over +// // whitespace: +// while ((curChar == '\n') || (curChar == '\t') || (curChar == '\r') || (curChar == ' ')) { +// curChar = value.charAt(index); +// index++; +// } +// if (index > 0) { +// index--; +// +// } +// start = start + index; +// startEndPositions[0] = start; +// startEndPositions[1] = start + value.trim().length(); +// } +// else if (node.getNodeType() == Node.ELEMENT_NODE) { +// IJSONElement element = (IJSONElement) node; +// Node child = element.getFirstChild(); +// if (child instanceof IJSONNode) { +// IJSONNode jsonChild = ((IJSONNode) child); +// startEndPositions[0] = jsonChild.getStartOffset(); +// startEndPositions[1] = jsonChild.getEndOffset(); +// } +// } +// } +// else if (FIRST_NON_WHITESPACE_TEXT.equals(selectionStrategy)) { +// // search through all child nodes and return range of +// // first non-whitespace +// // text node +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// NodeList nodes = node.getChildNodes(); +// for (int i = 0; i < nodes.getLength(); i++) { +// Node currentNode = nodes.item(i); +// if (currentNode.getNodeType() == Node.TEXT_NODE) { +// // TODO (Trung) I don't think we should call +// // getNodeValue(), trim(), length() +// // repeatedly. +// // This is inefficient, to improve use local +// // variables to store values. +// IJSONText textNode = (IJSONText) currentNode; +// if (textNode.getNodeValue().trim().length() > 0) { +// String value = textNode.getNodeValue(); +// int index = 0; +// int start = textNode.getStartOffset(); +// char curChar = value.charAt(index); +// // here we are finding start offset by +// // skipping over whitespace: +// while ((curChar == '\n') || (curChar == '\t') || (curChar == '\r') || (curChar == ' ')) { +// curChar = value.charAt(index); +// index++; +// } +// if (index > 0) { +// index--; +// +// } +// start = start + index; +// startEndPositions[0] = start; +// startEndPositions[1] = start + value.trim().length(); +// break; +// } +// } +// +// } +// } +// } +// +// else if (TEXT_ENTITY_REFERENCE.equals(selectionStrategy)) { +// if (node.getNodeType() == Node.ENTITY_REFERENCE_NODE) { +// startEndPositions[0] = region.getStartOffset(); +// startEndPositions[1] = region.getEndOffset(); +// } +// else if (node.getNodeType() == Node.ELEMENT_NODE) { +// /* +// * In this case the undeclared entity might be in one +// * of the attribute values. Search through the +// * attributes to find the range of the undeclared +// * entity. +// */ +// String entity = "&" + nameOrValue + ";"; //$NON-NLS-1$ //$NON-NLS-2$ +// NamedNodeMap attributes = node.getAttributes(); +// for (int i = 0; i < attributes.getLength(); i++) { +// IJSONAttr attr = (IJSONAttr) attributes.item(i); +// String nodeValue = attr.getNodeValue(); +// int index = nodeValue.indexOf(entity); +// if (index != -1) { +// startEndPositions[0] = attr.getValueRegionStartOffset() + index + 1; +// startEndPositions[1] = startEndPositions[0] + entity.length(); +// } +// } +// } +// +// } +// else if (VALUE_OF_ATTRIBUTE_WITH_GIVEN_VALUE.equals(selectionStrategy)) { +// // TODO (Trung) do we really need this strategy ? +// // If we know the name of the name of the attribute, we +// // can retrieve its value. +// // Hence, we can incoperate this strategy with +// // ATTRIBUTE_VALUE ? +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// // here we will search through all attributes for the +// // one with the +// // with the value we want: +// // TODO (Trung) I see a potential problem here. +// // What happens when there is another attribute having +// // the same value +// // with this attribute's buggy value ? +// // Need to solve when time permits. +// NamedNodeMap attributes = node.getAttributes(); +// for (int i = 0; i < attributes.getLength(); i++) { +// IJSONAttr attr = (IJSONAttr) attributes.item(i); +// String nodeValue = attr.getNodeValue().trim(); +// if (nodeValue.equals(nameOrValue)) { +// startEndPositions[0] = attr.getValueRegionStartOffset() + 1; +// startEndPositions[1] = startEndPositions[0] + nodeValue.length(); +// break; +// } +// } +// } +// } +// else if (ATTRIBUTE_NAME_LAST.equals(selectionStrategy)) { +// // underline the last attribute's name +// if (node.getNodeType() == Node.ELEMENT_NODE) { +// NamedNodeMap attributeMap = node.getAttributes(); +// final int length = attributeMap.getLength(); +// Node tempNode = null; +// Node attrNode = null; +// for (int i = 0; i < length; i++) { +// tempNode = attributeMap.item(i); +// if (tempNode != null && tempNode.getNodeName().equals(nameOrValue)) { +// attrNode = tempNode; +// } +// } +// IJSONNode attributeNode = (IJSONNode) (attrNode); +// if (attributeNode != null) { +// startEndPositions[0] = attributeNode.getStartOffset(); +// startEndPositions[1] = attributeNode.getStartOffset() + nameOrValue.length(); +// } +// } +// } +// } + return startEndPositions; + } + // catch (Exception e) { // e.printStackTrace(); + // } + finally { + } + // return null; + } + + /** + * Returns true if delegate validator is enabled based on Validation + * preferences + * + * @param file + * @return false if delegate validator is not enabled based on Validatoin + * preferences, true otherwise + */ + protected boolean isDelegateValidatorEnabled(IFile file) { + return true; + } + + protected String getErrorSide(IMessage message) { + // note that if the ERROR_SIDE is unspecified we return the default + // value ERROR_SIDE_LEFT + Object value = message.getAttribute(ERROR_SIDE); + return ERROR_SIDE_RIGHT.equals(value) ? ERROR_SIDE_RIGHT : ERROR_SIDE_LEFT; + } +} |