/*
* ====================================================================
*
* The ObjectStyle Group Software License, Version 1.0
*
* Copyright (c) 2005 The ObjectStyle Group and individual authors of the
* software. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: 1.
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. The end-user documentation
* included with the redistribution, if any, must include the following
* acknowlegement: "This product includes software developed by the ObjectStyle
* Group (http://objectstyle.org/)." Alternately, this acknowlegement may
* appear in the software itself, if and wherever such third-party
* acknowlegements normally appear. 4. The names "ObjectStyle Group" and
* "Cayenne" must not be used to endorse or promote products derived from this
* software without prior written permission. For written permission, please
* contact andrus@objectstyle.org. 5. Products derived from this software may
* not be called "ObjectStyle" nor may "ObjectStyle" appear in their names
* without prior written permission of the ObjectStyle Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the ObjectStyle Group. For more information on the ObjectStyle
* Group, please see <http://objectstyle.org/> .
*
*/
package org.objectstyle.wolips.wodclipse.core.document;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.rules.WhitespaceRule;
import org.objectstyle.wolips.bindings.wod.AbstractWodModel;
import org.objectstyle.wolips.bindings.wod.IWodBinding;
import org.objectstyle.wolips.bindings.wod.IWodElement;
import org.objectstyle.wolips.bindings.wod.IWodModel;
import org.objectstyle.wolips.bindings.wod.IWodUnit;
import org.objectstyle.wolips.bindings.wod.SimpleWodElement;
import org.objectstyle.wolips.bindings.wod.WodElementProblem;
import org.objectstyle.wolips.bindings.wod.WodProblem;
import org.objectstyle.wolips.wodclipse.core.Activator;
import org.objectstyle.wolips.wodclipse.core.completion.WodCacheEntry;
import org.objectstyle.wolips.wodclipse.core.completion.WodParserCache;
import org.objectstyle.wolips.wodclipse.core.parser.AssignmentOperatorWordDetector;
import org.objectstyle.wolips.wodclipse.core.parser.BindingNameRule;
import org.objectstyle.wolips.wodclipse.core.parser.BindingValueNamespaceRule;
import org.objectstyle.wolips.wodclipse.core.parser.BindingValueRule;
import org.objectstyle.wolips.wodclipse.core.parser.CloseDefinitionWordDetector;
import org.objectstyle.wolips.wodclipse.core.parser.ElementNameRule;
import org.objectstyle.wolips.wodclipse.core.parser.ElementTypeOperatorWordDetector;
import org.objectstyle.wolips.wodclipse.core.parser.ElementTypeRule;
import org.objectstyle.wolips.wodclipse.core.parser.EndAssignmentWordDetector;
import org.objectstyle.wolips.wodclipse.core.parser.ICommentRule;
import org.objectstyle.wolips.wodclipse.core.parser.OpenDefinitionWordDetector;
import org.objectstyle.wolips.wodclipse.core.parser.RulePosition;
import org.objectstyle.wolips.wodclipse.core.parser.StringLiteralRule;
import org.objectstyle.wolips.wodclipse.core.parser.WOOGNLRule;
import org.objectstyle.wolips.wodclipse.core.parser.WodScanner;
/**
* @author mschrag
*/
public class DocumentWodModel extends AbstractWodModel {
private IFile _wodFile;
private IDocument _document;
public DocumentWodModel(IFile wodFile, IDocument document) {
_wodFile = wodFile;
_document = document;
parse();
}
public IDocument getDocument() {
return _document;
}
public void addParseProblem(IWodElement element, String message, RulePosition rulePosition, boolean warning) {
Position position = rulePosition.getPosition();
try {
int lineNumber = _document.getLineOfOffset(position.getOffset());
WodProblem problem;
if (element != null) {
problem = new WodElementProblem(element, message, position, lineNumber, warning);
}
else {
problem = new WodProblem(message, position, lineNumber, warning);
}
addParseProblem(problem);
}
catch (BadLocationException e) {
Activator.getDefault().log(e);
}
}
// MS: all these savedRulePositions should be switched to instead use the rulePositions stack ...
protected synchronized void parse() {
clear();
WodScanner scanner = WodScanner.wodScannerForDocument(_document);
DocumentWodBinding lastBinding = null;
DocumentWodElement element = null;
RulePosition tentativeElementName = null;
RulePosition savedRulePosition = null;
RulePosition savedRulePosition2 = null;
RulePosition savedRulePosition3 = null;
RulePosition lastRulePosition = null;
ForgivingStack<RulePosition> rulePositions = new ForgivingStack<RulePosition>();
// boolean stringLiteralIsABindingName = false;
RulePosition rulePosition;
while ((rulePosition = scanner.nextRulePosition()) != null) {
boolean whitespace = false;
boolean comment = false;
if (RulePosition.isRulePositionOfType(rulePosition, WhitespaceRule.class)) {
whitespace = true;
}
else if (RulePosition.isRulePositionOfType(rulePosition, ICommentRule.class)) {
comment = true;
if (lastBinding != null) {
String commentText = rulePosition._getTextWithoutException();
if (commentText != null && commentText.startsWith("//")) {
commentText = commentText.substring(2).trim();
if ("VALID".equalsIgnoreCase(commentText)) {
lastBinding.setValidate(false);
}
}
}
else {
String commentText = rulePosition._getTextWithoutException();
if (commentText != null && commentText.startsWith("//")) {
commentText = commentText.substring(2).trim();
if (commentText.toLowerCase().startsWith("inherit ")) {
String componentName = commentText.substring("inherit ".length()).trim();
try {
WodParserCache inheritCache = WodParserCache.parser(_wodFile.getProject(), componentName);
WodCacheEntry wodCacheEntry = inheritCache.getWodEntry();
IWodModel parentWodModel = wodCacheEntry.getModel();
for (IWodElement parentWodElement : parentWodModel.getElements()) {
SimpleWodElement inheritedWodElement = new SimpleWodElement(parentWodElement);
inheritedWodElement.setInherited(true);
addElement(inheritedWodElement);
}
}
catch (Throwable t) {
addParseProblem(element, "WOD inheritance of '" + componentName + "' failed: " + t.getMessage() + ".", rulePosition, false);
}
}
}
}
}
else if (RulePosition.isRulePositionOfType(rulePosition, ElementNameRule.class)) {
if (RulePosition.isOperatorOfType(lastRulePosition, OpenDefinitionWordDetector.class) || RulePosition.isOperatorOfType(lastRulePosition, EndAssignmentWordDetector.class)) {
savedRulePosition2 = rulePosition;
if (lastRulePosition != null && !RulePosition.isOperatorOfType(lastRulePosition, CloseDefinitionWordDetector.class)) {
tentativeElementName = rulePosition;
}
// leave old savedRulePosition
}
else {
if (lastRulePosition != null && !RulePosition.isOperatorOfType(lastRulePosition, CloseDefinitionWordDetector.class)) {
addParseProblem(element, "The element name '" + rulePosition._getTextWithoutException() + "' must start a WOD declaration", rulePosition, false);
}
savedRulePosition = rulePosition;
element = null;
}
}
else if (RulePosition.isOperatorOfType(rulePosition, ElementTypeOperatorWordDetector.class)) {
if (!RulePosition.isRulePositionOfType(lastRulePosition, ElementNameRule.class) && !RulePosition.isRulePositionOfType(lastRulePosition, BindingValueNamespaceRule.class)) {
addParseProblem(element, "A ':' can only appear after an element name or a binding value namespace.", rulePosition, false);
}
}
else if (RulePosition.isRulePositionOfType(rulePosition, ElementTypeRule.class)) {
if (tentativeElementName != null) {
addParseProblem(element, "The element name ' " + tentativeElementName._getTextWithoutException() + "' must start a WOD declaration", tentativeElementName, false);
tentativeElementName = null;
savedRulePosition = null;
}
else if (!RulePosition.isOperatorOfType(lastRulePosition, ElementTypeOperatorWordDetector.class)) {
addParseProblem(element, "The element type '" + rulePosition._getTextWithoutException() + "' can only appear after a ':'", rulePosition, false);
}
else {
rulePositions.clear();
element = new DocumentWodElement(savedRulePosition, rulePosition);
addElement(element);
savedRulePosition = null;
}
}
else if (RulePosition.isOperatorOfType(rulePosition, OpenDefinitionWordDetector.class)) {
if (!RulePosition.isRulePositionOfType(lastRulePosition, ElementTypeRule.class)) {
addParseProblem(element, "A '{' can only appear after an element type", rulePosition, false);
}
}
else if (RulePosition.isRulePositionOfType(rulePosition, WOOGNLRule.class)) {
boolean ognlIsValue = RulePosition.isOperatorOfType(lastRulePosition, AssignmentOperatorWordDetector.class);
boolean ognlIsName = !ognlIsValue && (RulePosition.isOperatorOfType(lastRulePosition, EndAssignmentWordDetector.class) || RulePosition.isOperatorOfType(lastRulePosition, OpenDefinitionWordDetector.class));
if (!ognlIsValue && !ognlIsName) {
addParseProblem(element, "The OGNL value " + rulePosition._getTextWithoutException() + " can only appear after a '{', '=', or ';'.", rulePosition, false);
savedRulePosition = null;
}
else if (ognlIsName) {
savedRulePosition = rulePosition;
}
else if (ognlIsValue) {
lastBinding = addBinding(element, savedRulePosition2, savedRulePosition, null, rulePosition, scanner);
savedRulePosition = null;
savedRulePosition2 = null;
savedRulePosition3 = null;
}
}
else if (RulePosition.isRulePositionOfType(rulePosition, StringLiteralRule.class)) {
boolean literalIsValue = RulePosition.isOperatorOfType(lastRulePosition, AssignmentOperatorWordDetector.class);
boolean literalIsName = !literalIsValue && (RulePosition.isOperatorOfType(rulePositions.peek(), EndAssignmentWordDetector.class) || RulePosition.isOperatorOfType(rulePositions.peek(), OpenDefinitionWordDetector.class));
if (!literalIsValue && !literalIsName) {
addParseProblem(element, "The string literal '" + rulePosition._getTextWithoutException() + "' can only appear after a '{', '=', or ';'.", rulePosition, false);
savedRulePosition = null;
}
else if (literalIsName) {
savedRulePosition = rulePosition;
}
else if (literalIsValue) {
lastBinding = addBinding(element, savedRulePosition2, savedRulePosition, null, rulePosition, scanner);
savedRulePosition = null;
savedRulePosition2 = null;
savedRulePosition3 = null;
}
tentativeElementName = null;
}
else if (RulePosition.isRulePositionOfType(rulePosition, BindingNameRule.class)) {
if (!RulePosition.isOperatorOfType(lastRulePosition, OpenDefinitionWordDetector.class) && !RulePosition.isOperatorOfType(lastRulePosition, EndAssignmentWordDetector.class) && !RulePosition.isOperatorOfType(lastRulePosition, ElementTypeOperatorWordDetector.class)) {
addParseProblem(element, "The binding name '" + rulePosition._getTextWithoutException() + "' can only appear after a '{' or a ';'", rulePosition, false);
}
savedRulePosition = rulePosition;
lastBinding = null;
}
else if (RulePosition.isOperatorOfType(rulePosition, AssignmentOperatorWordDetector.class)) {
if (!RulePosition.isRulePositionOfType(lastRulePosition, BindingNameRule.class) && !RulePosition.isRulePositionOfType(lastRulePosition, StringLiteralRule.class)) {
addParseProblem(element, "An '=' can only appear after a binding name", rulePosition, false);
}
}
else if (RulePosition.isRulePositionOfType(rulePosition, BindingValueNamespaceRule.class)) {
if (!RulePosition.isOperatorOfType(lastRulePosition, AssignmentOperatorWordDetector.class)) {
addParseProblem(element, "The binding value namespace '" + rulePosition._getTextWithoutException() + "' can only appear after an '='", rulePosition, false);
}
else {
savedRulePosition3 = rulePosition;
}
}
else if (RulePosition.isRulePositionOfType(rulePosition, BindingValueRule.class)) {
if (!RulePosition.isOperatorOfType(lastRulePosition, AssignmentOperatorWordDetector.class) && !(RulePosition.isOperatorOfType(lastRulePosition, ElementTypeOperatorWordDetector.class) && RulePosition.isRulePositionOfType(savedRulePosition3, BindingValueNamespaceRule.class))) {
addParseProblem(element, "The binding value '" + rulePosition._getTextWithoutException() + "' can only appear after an '=' or a 'xxx:'", rulePosition, false);
}
else {
lastBinding = addBinding(element, savedRulePosition2, savedRulePosition, savedRulePosition3, rulePosition, scanner);
}
savedRulePosition = null;
savedRulePosition2 = null;
savedRulePosition3 = null;
}
else if (RulePosition.isOperatorOfType(rulePosition, EndAssignmentWordDetector.class)) {
if (!RulePosition.isRulePositionOfType(lastRulePosition, BindingValueRule.class) && !RulePosition.isRulePositionOfType(lastRulePosition, StringLiteralRule.class) && !RulePosition.isRulePositionOfType(lastRulePosition, WOOGNLRule.class)) {
addParseProblem(element, "A ';' can only appear after a binding value", rulePosition, false);
}
}
else if (RulePosition.isOperatorOfType(rulePosition, CloseDefinitionWordDetector.class)) {
if (element != null) {
element.setEndOffset(rulePosition.getTokenOffset() + 1);
}
if (!RulePosition.isOperatorOfType(lastRulePosition, OpenDefinitionWordDetector.class) && !RulePosition.isOperatorOfType(lastRulePosition, EndAssignmentWordDetector.class) && !RulePosition.isRulePositionOfType(lastRulePosition, BindingValueRule.class) && !RulePosition.isRulePositionOfType(lastRulePosition, StringLiteralRule.class) && !RulePosition.isRulePositionOfType(lastRulePosition, WOOGNLRule.class)) {
addParseProblem(element, "A '}' can only appear after a ';' or a '{'", rulePosition, false);
}
else {
element = null;
}
lastBinding = null;
}
else {
addParseProblem(element, "'" + rulePosition._getTextWithoutException() + "' is an unknown keyword", rulePosition, false);
}
if (!whitespace && !comment) {
lastRulePosition = rulePosition;
rulePositions.push(rulePosition);
}
}
if (lastRulePosition != null && !RulePosition.isOperatorOfType(lastRulePosition, CloseDefinitionWordDetector.class)) {
addParseProblem(element, "The last entry in a WOD file must be a '}'.", lastRulePosition, false);
}
}
protected DocumentWodBinding addBinding(DocumentWodElement element, RulePosition namespaceRulePosition, RulePosition nameRulePosition, RulePosition valueNamespaceRulePosition, RulePosition valueRulePosition, WodScanner scanner) {
DocumentWodBinding binding = null;
if (element == null) {
addParseProblem(element, "A binding must appear in a declaration", valueRulePosition, false);
}
else if (nameRulePosition == null) {
addParseProblem(element, "A binding must have a name", valueRulePosition, false);
}
else if (valueRulePosition == null) {
addParseProblem(element, "A binding must have a value", valueRulePosition, false);
}
else {
binding = new DocumentWodBinding(namespaceRulePosition, nameRulePosition, valueNamespaceRulePosition, valueRulePosition);
element.addBinding(binding);
}
return binding;
}
public String getName() {
return _wodFile.getName();
}
public IFile getWodFile() {
return _wodFile;
}
public int getStartOffset() {
return 0;
}
public int getEndOffset() {
return _document.getLength();
}
public IWodElement getWodElementAtIndex(int index) {
IWodElement elementAtIndex = null;
Iterator<IWodElement> elementsIter = getElements().iterator();
while (elementAtIndex == null && elementsIter.hasNext()) {
IWodElement element = elementsIter.next();
if (isIndexContainedByWodUnit(index, element)) {
elementAtIndex = element;
}
}
return elementAtIndex;
}
public IWodUnit getWodUnitAtIndex(int index) {
IWodUnit wodUnit = null;
IWodElement elementAtIndex = getWodElementAtIndex(index);
if (elementAtIndex != null) {
Iterator<IWodBinding> bindingsIter = elementAtIndex.getBindings().iterator();
while (wodUnit == null && bindingsIter.hasNext()) {
IWodBinding binding = bindingsIter.next();
if (isIndexContainedByWodUnit(index, binding)) {
wodUnit = binding;
}
}
if (wodUnit == null) {
wodUnit = elementAtIndex;
}
}
if (wodUnit == null) {
wodUnit = this;
}
return wodUnit;
}
protected boolean isIndexContainedByWodUnit(int index, IWodUnit wodUnit) {
return index >= wodUnit.getStartOffset() && index <= wodUnit.getEndOffset();
}
protected static class ForgivingStack<T> {
private List<T> _contents;
public ForgivingStack() {
_contents = new LinkedList<T>();
}
public void push(T obj) {
_contents.add(0, obj);
}
public void clear() {
_contents.clear();
}
public T peek() {
return peek(0);
}
public T peek(int offset) {
T obj = null;
if (_contents.size() > offset) {
obj = _contents.get(offset);
}
return obj;
}
}
}