/******************************************************************************* * Copyright (c) 2013 S.Boyko 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: * S.Boyko - initial API and implementation *******************************************************************************/ package org.eclipse.m2m.internal.tests.qvt.oml.debugger; import java.text.MessageFormat; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import org.eclipse.m2m.internal.qvt.oml.common.util.StringLineNumberProvider; /** * Represents a marked transformation file, parses it and produces marker name to line number * mappings along with "clean text" - without markers. * A line marker is set in the transformation file to denote a simple label and maybe an * attempt to set up a breakpoint.<br> * An assert annotation is set somewhere in the file to check up an equality type condition.<br> * A syntax of <b>markers</b> is the following:<br> * <ol> * <li> A marker token starts at <i>the first symbol</i> of the line<br> * <li> A marker token begins with a <i>prefix</i> "<code>--!@#</code>" (<code>LINE_MARKER_PREFIX</code>)<br> * <li> A substring from the first symbol after prefix and up to the first whitespace * character is the marker's <i>name</i><br> * <li> A name <i>cannot</i> start with a digit<br> * <li> To denote an attempt to set up a breakpoint on the line <i>following</i> the one * with the marker put a "breakpoint" keyword (<code>BREAKPOINT</code>) after the name (separated by a * whitespace)<br> * <li> To denote that an attempt to set a breakpoint <i>must fail</i> put a "fail" (<code>BREAKPOINT_FAIL</code>) * keyword after "breakpoint keyword"<br> * </ol> * Examples:<br> * <code>--!@#b1</code> // simply names the next line as "b1"<br> * <code>--!@#b1 breakpoint</code> // denotes an attempt to set a breakpoint on the next line * which must fall into success<br> * <code>--!@#b1 breakpoint fail</code> // the same as the previuos but an attempt must fail<br> * A syntax of <b>assert annotations</b> is the following:<br> * <ol> * <li> An annotation token starts at <i>the first symbol</i> of the line<br> * <li> An annotation token begins with a <i>prefix</i> "<code>--!@@</code>"<br> * <li> A substring from the first symbol after prefix and up to the first "<code>@</code>" character is the expression * being examined. This must be a valid OCL boolean expression. It's asserted to be true * </ol> * Examples:<br> * <code>--!@@model.name = '123123123'</code> // checks up given expression */ class MarkedTransformation { /** * @param markedData - a <b><i>text</i></b> of the transformation with line markers */ public MarkedTransformation(String markedData) { myLineRecs = new LinkedHashMap<Object,LineMarker>(); myCleanText = parseAndFilterMarkedFile(markedData); } /** * Parses a transformation file with markers (see {@link LineMarker})<br> * Fills in myLineRecs and myLineToName maps * @param fileName - a name of file to be parsed * @return a text of the file without markers */ public String parseAndFilterMarkedFile(String markedData) { String cleanData = ""; //$NON-NLS-1$ StringLineNumberProvider lnp = new StringLineNumberProvider(markedData); String[] lines = new String[lnp.getLineCount()]; int prevEnd = 0; for (int i = 0; i < lnp.getLineCount(); i++) { lines[i] = markedData.substring(prevEnd, lnp.getLineEnd(i + 1) + 1); prevEnd = lnp.getLineEnd(i + 1) + 1; } /* Parsing a file for line-markers */ int lineCounter = 0; boolean actualFlag; for (int absoluteLine = 0; absoluteLine < lines.length; absoluteLine++) { final String line = lines[absoluteLine]; lineCounter++; actualFlag = true; if (line.startsWith(LINE_MARKER_PREFIX)) { int nameLen = 0; boolean breakpoint = false; boolean fail = false; int failOfs = 0; int bpOfs = 0; // A state-machine int state = 0; // looking for name for (int i = LINE_MARKER_PREFIX.length(); (i < line.length()) && (state != -1); i++) { switch (state) { case 0: // looking for name if (Character.isWhitespace(line.charAt(i))) { if (nameLen == 0) { state = -1; // exit } else { state = 1; // looking for "breakpoint" } } else { if (Character.isDigit(line.charAt(i)) && (nameLen == 0)) { System.err.println(MessageFormat.format(MARKER_NAME_START_MESSAGE, new Object[]{new Integer(absoluteLine), line.trim()})); state = -1; // exit } else { nameLen++; } } break; case 1: // looking for "breakpoint" if (Character.isWhitespace(line.charAt(i))) { if (bpOfs > 0) { breakpoint = bpOfs == BREAKPOINT.length(); state = (breakpoint)?2:-1; // looking for "fail" if a breakpoint found, exit otherwise } } else { if (line.charAt(i) == BREAKPOINT.charAt(bpOfs)) { bpOfs++; } else { state = -1; // exit } } break; case 2: // looking for "fail" if (Character.isWhitespace(line.charAt(i))) { if (failOfs > 0) { fail = failOfs == BREAKPOINT_FAIL.length(); state = -1; // exit } } else { if (line.charAt(i) == BREAKPOINT_FAIL.charAt(failOfs)) { failOfs++; } else { state = -1; // exit } } break; case -1: // exit; break; } } if (nameLen == 0) { System.err.println(MessageFormat.format(NO_NAME_FOUND_MESSAGE, new Object[]{new Integer(absoluteLine), line.trim()})); } else { String name = line.substring(LINE_MARKER_PREFIX.length(), LINE_MARKER_PREFIX.length() + nameLen); if (myLineRecs.containsKey(name)) { throw new RuntimeException(MessageFormat.format(DUPLICATE_MARKER_NAME_MESSAGE, new Object[]{name, new Integer(absoluteLine), line.trim()})); } LineMarker marker = new LineMarker(lineCounter, absoluteLine, name, breakpoint, fail); // The same hash stores line numbers and names as keys for the same object myLineRecs.put(name, marker); myLineRecs.put(new Integer(lineCounter), marker); actualFlag = false; lineCounter--; } } if (actualFlag) { cleanData = cleanData.concat(line); } } return cleanData; } /** * @param name - Marker's name * @return a line marker with given name */ public LineMarker getLineMarker(String name) { return (LineMarker) myLineRecs.get(name); } /** * @param lineNumber - number of line in a <b><i>clean</i></b> file - without markers * @return a line marker of given line */ public LineMarker getLineMarker(int lineNumber) { return (LineMarker) myLineRecs.get(new Integer(lineNumber)); } /** * @return Clean transformation text - without any markers */ public String getCleanText() { return myCleanText; } /** * @return A collection of all the markers specified for this transformation */ public Collection<LineMarker> getBreakpointLineMarkers() { Collection<LineMarker> result = new LinkedHashSet<LineMarker>(); for (LineMarker rec : myLineRecs.values()) { if (rec.breakpoint) { result.add(rec); } } return result; } /** * Represents a line marker */ public class LineMarker { /** * Creates a new marker object * @param lineNumber - line number in clean text * @param absoluteLine - line number in marked text * @param name - marker name * @param breakpoint - "breakpoint" * @param fail - "fail" */ public LineMarker(int lineNumber, int absoluteLine, String name, boolean breakpoint, boolean fail) { this.name = name; this.lineNumber = lineNumber; this.absoluteLine = absoluteLine; this.breakpoint = breakpoint; this.fail = fail; } public String toString() { return MessageFormat.format("line {4}({0}): \"{1} {2} {3}\"", new Object[] { new Integer(lineNumber), name, ((breakpoint)?BREAKPOINT:""), ((fail)?BREAKPOINT_FAIL:""), new Integer(absoluteLine)}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } public final int lineNumber; public final int absoluteLine; public final String name; public final boolean breakpoint; public final boolean fail; } private final HashMap<Object, LineMarker> myLineRecs; private final String myCleanText; private static final String LINE_MARKER_PREFIX = "--!@#"; //$NON-NLS-1$ private static final String BREAKPOINT = "breakpoint"; //$NON-NLS-1$ private static final String BREAKPOINT_FAIL = "fail"; //$NON-NLS-1$ private static final String MARKER_NAME_START_MESSAGE = "A line marker name cannot start with a digit: line {0}:{1}"; //$NON-NLS-1$ private static final String NO_NAME_FOUND_MESSAGE = "No name found. Line marker ignored: line {0}:{1}"; //$NON-NLS-1$ private static final String DUPLICATE_MARKER_NAME_MESSAGE = "Duplicate marker name \"{0}\": line {1}:{2}"; //$NON-NLS-1$ }