/******************************************************************************* * Copyright (c) 2010, 2011 Obeo. * 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: * Obeo - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.docs.intent.parser.internal; import java.util.ArrayList; import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.mylyn.docs.intent.core.compiler.CompilationMessageType; import org.eclipse.mylyn.docs.intent.core.compiler.CompilationStatus; import org.eclipse.mylyn.docs.intent.core.compiler.CompilationStatusSeverity; import org.eclipse.mylyn.docs.intent.core.compiler.CompilerFactory; import org.eclipse.mylyn.docs.intent.core.document.IntentDocument; import org.eclipse.mylyn.docs.intent.core.document.IntentDocumentFactory; import org.eclipse.mylyn.docs.intent.core.document.IntentGenericElement; import org.eclipse.mylyn.docs.intent.core.document.IntentSection; import org.eclipse.mylyn.docs.intent.parser.internal.state.IntentGenericState; import org.eclipse.mylyn.docs.intent.parser.internal.state.SChapter; import org.eclipse.mylyn.docs.intent.parser.internal.state.SDocument; import org.eclipse.mylyn.docs.intent.parser.internal.state.SSection; import org.eclipse.mylyn.docs.intent.parser.modelingunit.ParseException; import org.eclipse.mylyn.docs.intent.serializer.IntentPositionManager; /** * Builder for an IntentStructuredElement : use a state machine to build Intent elements according to the * signal sent by a IntentParser. * * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a> * @author <a href="mailto:william.piers@obeo.fr">William Piers</a> */ public class IntentBuilder { /** * The {@link IntentPositionManager} holding position information. */ private IntentPositionManager positionManager; /** * CurrentRoot of the document : can be a IntentStructuredElement (Chapter, Section), a Modeling Unit or a * Description Unit. */ private EObject currentRoot; /** * List of the created roots of the document. */ private List<EObject> roots; /** * Represents the current State of the parser. */ private IntentGenericState currentState; /** * Represents the currentImbricationLevel. */ private int currentImbricationLevel; /** * The root complete level. */ private String rootCompleteLevel; /** * IntentBuilder constructor. * * @param positionManager * the positionManager where to register positions * @param rootCompleteLevel * the root complete level */ public IntentBuilder(IntentPositionManager positionManager, String rootCompleteLevel) { roots = new ArrayList<EObject>(); this.positionManager = positionManager; this.rootCompleteLevel = rootCompleteLevel; } /** * Returns the generated roots of the parsed content. * * @return the generated roots of the parsed content */ public List<EObject> getRoots() { return roots; } /** * Returns the generated root of the parsed content. * * @return the generated root of the parsed content * @throws ParseException * if there isn't exactly one generated root */ public EObject getRoot() throws ParseException { if (roots.size() != 1) { throw new ParseException("The elements described should be enclosed in a super element. "); } if (currentImbricationLevel > 0) { addStatusOnElement(roots.get(0), "Syntax error : insert \"}\" to close this element."); } return roots.get(0); } /** * Increases the current imbrication level. */ public void increaseImbricationLevel() { this.currentImbricationLevel++; } /** * Decreases the current imbrication level. */ public void decreaseImbricationLevel() { this.currentImbricationLevel--; } /** * Indicates the beginning of a Document. * * @param offset * the begin offset of the document * @param declarationLength * the declaration length of the document * @throws ParseException * if the document can't be opened in the current state of the parser */ public void beginDocument(int offset, int declarationLength) throws ParseException { if (currentState == null) { currentRoot = IntentDocumentFactory.eINSTANCE.createIntentDocument(); roots.add(currentRoot); currentState = new SDocument(offset, declarationLength, null, (IntentDocument)currentRoot, positionManager); increaseImbricationLevel(); } else { throw new ParseException("Can't open any document here.", offset, declarationLength); } } /** * Indicates the beginning of a Chapter. * * @param offset * the begin offset of the Chapter * @param declarationLength * the declaration length of the Chapter * @param title * the chapter title * @throws ParseException * if the title cannot be parsed */ public void beginChapter(int offset, int declarationLength, String title) throws ParseException { increaseImbricationLevel(); if (currentState == null) { currentRoot = IntentDocumentFactory.eINSTANCE.createIntentSection(); roots.add(currentRoot); currentState = new SChapter(offset, declarationLength, null, (IntentSection)currentRoot, positionManager, title, rootCompleteLevel); } else { currentState = currentState.beginChapter(offset, declarationLength, title); } } /** * Indicates the beginning of a IntentSection. * * @param offset * the begin offset of the Section * @param declarationLength * the declaration length of the Section * @param title * the section title * @throws ParseException * if the title cannot be parsed */ public void beginSection(int offset, int declarationLength, String title) throws ParseException { increaseImbricationLevel(); if (currentState == null) { currentRoot = IntentDocumentFactory.eINSTANCE.createIntentSection(); roots.add(currentRoot); currentState = new SSection(offset, declarationLength, null, (IntentSection)currentRoot, positionManager, title, rootCompleteLevel); } else { currentState = currentState.beginSection(offset, declarationLength, title); } } /** * Indicates the end of a Structured Element (Document, Chapter or Section). * * @param offset * the ending offset of the structured element * @throws ParseException * if there is no structured element to end */ public void endStructuredElement(int offset) throws ParseException { if (currentImbricationLevel == 0) { throw new ParseException("There is no element to close.", offset - 1, 1); } if (currentState != null) { currentState = currentState.endStructuredElement(offset); decreaseImbricationLevel(); } else { addStatusOnElement(getRoot(), "Syntax Error on token \"{\" : no element to close."); } } /** * Adds a status with the given message on the given element. * * @param element * the incorrect element * @param statusMessage * the message of the status to add */ public void addStatusOnElement(EObject element, String statusMessage) { if (element instanceof IntentGenericElement) { CompilationStatus compilationStatus = CompilerFactory.eINSTANCE.createCompilationStatus(); compilationStatus.setMessage(statusMessage); compilationStatus.setSeverity(CompilationStatusSeverity.ERROR); compilationStatus.setTarget((IntentGenericElement)element); compilationStatus.setType(CompilationMessageType.INVALID_REFERENCE_ERROR); ((IntentGenericElement)element).getCompilationStatus().add(compilationStatus); } } /** * Indicates a Modeling Unit with the given content. * * @param offset * the begin offset of the modeling unit * @param modelingUnitContent * the content of this modeling Unit * @throws ParseException * if the modeling unit parser detect any parse error */ public void modelingUnitContent(int offset, String modelingUnitContent) throws ParseException { currentState = currentState.modelingUnitContent(offset, modelingUnitContent.length(), modelingUnitContent); } /** * Indicates a Description Unit with the given Content. * * @param offset * the begin offset of the description unit * @param descriptionUnitContent * the content of the description Unit * @throws ParseException * if the description unit parser detect any parse error. */ public void descriptionUnitContent(int offset, String descriptionUnitContent) throws ParseException { currentState = currentState.descriptionUnitContent(offset, descriptionUnitContent.length(), formatUsingImbricationLevel(descriptionUnitContent, false)); } /** * Uses the current imbrication level to remove non-relevant tabulations. * * @param content * the content to format * @param isModelingUnit * indicates if the content describes a modelingUnit * @return the given content from which we removed any non-relevant tabulations */ private String formatUsingImbricationLevel(String content, boolean isModelingUnit) { StringBuilder intentContent = new StringBuilder(); String[] lines = content.split("\n"); // If the content is a Modeling Unit, we must temporary increase the imbrication level int temporaryIncrease = 0; if (isModelingUnit) { temporaryIncrease++; } // For each line for (int i = 0; i < lines.length; i++) { String currentLine = lines[i]; currentLine = removeBeginningSpaces(currentLine); // We remove the N first tabulations, with N equals to the currentImbricationLevel for (int j = 0; j < this.currentImbricationLevel + temporaryIncrease; j++) { if (currentLine.indexOf("\t") == 0) { currentLine = currentLine.replaceFirst("\t", ""); } } intentContent.append(currentLine + "\n"); } return intentContent.toString(); } /** * Remove all spaces at the beginning of the line. * * @param line * the line * @return the fixed line */ private String removeBeginningSpaces(String line) { if (line != null && line.length() > 0) { int offset = 0; while (offset < line.length() && line.charAt(offset) == ' ') { offset++; } return line.substring(offset); } return ""; } }