blob: b9d50ae62151352eb9c498dd61bef906695fd4e3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2018 Borland Software Corporation and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Borland Software Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.m2m.tests.qvt.oml.util;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.core.runtime.Assert;
import org.eclipse.m2m.internal.qvt.oml.common.util.StringLineNumberProvider;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class SourceAnnotationReader {
private String fSource;
private List<CommentTag> fcommentTags;
private List<AnnotationData> fAnnotations;
private SAXParser fParser;
private StringLineNumberProvider fLineNumberProvider;
public SourceAnnotationReader(String sourceCode) {
this(sourceCode, true);
}
public SourceAnnotationReader(String sourceCode, boolean trimAnnotationFromSource) {
fSource = sourceCode;
fcommentTags = new ArrayList<CommentTag>();
fAnnotations = new ArrayList<AnnotationData>();
try {
fParser = SAXParserFactory.newInstance().newSAXParser();
} catch (Exception e) {
throw new RuntimeException("Failed to create parser"); //$NON-NLS-1$
}
parse();
if(trimAnnotationFromSource){
makeSourceAdjustments();
}
fLineNumberProvider = new StringLineNumberProvider(fSource);
}
public String getSource() {
return fSource;
}
public List<AnnotationData> getAnnotations() {
return Collections.unmodifiableList(fAnnotations);
}
private void makeSourceAdjustments() {
StringBuilder buf = new StringBuilder(fSource);
int shiftOffset = 0;
for (AnnotationData annotation : fAnnotations) {
RegionInfo reg = annotation.getAnnotatedRegion();
CommentTag open = reg.openTag;
CommentTag close = reg.closeTag;
shiftOffset = trimTag(buf, open, shiftOffset);
if(open != close) {
annotation.fRegion.fStartOffset -= (shiftOffset);
annotation.fRegion.fEndOffset -= (shiftOffset);
shiftOffset = trimTag(buf, close, shiftOffset);
} else {
if(annotation.fRegion.fStartOffset > 0) {
annotation.fRegion.fStartOffset -= (shiftOffset);
annotation.fRegion.fEndOffset -= (shiftOffset);
}
}
}
fSource = buf.toString();
}
private void parse() {
int length = fSource.length();
int leftOffset = -1;
int rightOffset = -1;
for(int i = 0; i < length; i++) {
char c = fSource.charAt(i);
if(c == '/' && i + 1 < length && fSource.charAt(i + 1) == '*') {
leftOffset = i;
} else if(c == '*' && i + 1 < length && fSource.charAt(i + 1) == '/') {
rightOffset = i;
}
if(leftOffset >= 0 && rightOffset >= 0) {
String text = fSource.substring(leftOffset + 2, rightOffset);
fcommentTags.add(new CommentTag(leftOffset, rightOffset + 2, text));
leftOffset = -1;
rightOffset = -1;
}
}
processCommentTags();
}
private void processCommentTags() {
List<RegionInfo> result = new ArrayList<RegionInfo>();
CommentTag leftTag = null;
CommentTag rightTag = null;
for (CommentTag tag : fcommentTags) {
if(leftTag == null) {
if(tag.fIsSimpleTag) {
int endPos = tag.fEndOffset;
if(tag.fEndOffset == fSource.length() - 1) {
endPos = tag.fStartOffset - 1;
} else if(tag.fStartOffset == 0) {
endPos = 0;
}
RegionInfo reg = new RegionInfo(endPos, endPos, tag.fTextPart);
handleRegion(reg, tag, tag);
} else {
leftTag = tag;
}
continue;
}
if(rightTag == null) {
rightTag = tag;
}
RegionInfo reg = new RegionInfo(leftTag.fEndOffset, rightTag.fStartOffset, leftTag.fTextPart + rightTag.fTextPart);
result.add(reg);
handleRegion(reg, leftTag, rightTag);
leftTag = rightTag = null;
}
}
private void handleRegion(RegionInfo regionInfo, CommentTag openTag, CommentTag closeTag) {
try {
regionInfo.openTag = openTag;
regionInfo.closeTag = closeTag;
parseXMLContents(regionInfo);
} catch (Exception e) {
throw new RuntimeException("Error in source test annotation tag at:" + openTag + closeTag, e); //$NON-NLS-1$
}
}
private void parseXMLContents(final RegionInfo regionInfo) throws Exception {
class XMLHandler extends DefaultHandler {
@Override
public void startElement (String uri, String localName, String qName, Attributes attributes) throws SAXException {
AnnotationData data = new AnnotationData(qName, regionInfo);
for(int i = 0; i < attributes.getLength(); i++) {
data.addAttr(attributes.getQName(i), attributes.getValue(i));
}
SourceAnnotationReader.this.fAnnotations.add(data);
}
};
XMLHandler handler = new XMLHandler();
String xmlBody = regionInfo.fMetaBody;
fParser.parse(new InputSource(new StringReader(xmlBody)), handler);
}
public class AnnotationData {
private Map<String, String> fAttrs = Collections.emptyMap();
private RegionInfo fRegion;
private String fName;
private int fLineNum;
private AnnotationData(String name, RegionInfo region) {
fRegion = region;
fName = name;
fLineNum = -1;
}
private void addAttr(String name, String value) {
if(fAttrs.isEmpty()) {
fAttrs = new HashMap<String, String>(3);
}
fAttrs.put(name, value);
}
public String getName() {
return fName;
}
public String getAttrValue(String name) {
return fAttrs.get(name);
}
public RegionInfo getAnnotatedRegion() {
return fRegion;
}
public int getLineNumber() {
if(fLineNum < 0) {
fLineNum = SourceAnnotationReader.this.fLineNumberProvider.getLineNumber(fRegion.getOffset());
}
return fLineNum;
}
@Override
public String toString() {
return fName + " - " + fRegion + "(line:" + getLineNumber() + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
}
public static class RegionInfo {
private int fStartOffset;
private int fEndOffset;
private final String fMetaBody;
private CommentTag openTag;
private CommentTag closeTag;
private RegionInfo(int startOffset, int endOffset, String metaDataContents) {
fStartOffset = startOffset;
fEndOffset = endOffset;
fMetaBody = metaDataContents;
}
public CommentTag getOpenTag() {
return openTag;
}
public CommentTag getCloseTag() {
return closeTag;
}
public int getOffset() {
return fStartOffset;
}
public int getLength() {
return fEndOffset - fStartOffset;
}
@Override
public String toString() {
return fMetaBody + " (" + fEndOffset + ", " + fStartOffset + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
/**
* @return new shift offset
*/
private static int trimTag(StringBuilder buf, CommentTag tag, int shiftOffset) {
int startOffset = tag.fStartOffset - shiftOffset;
int endOffset = tag.fEndOffset - shiftOffset;
buf.replace(startOffset, endOffset, ""); //$NON-NLS-1$
return shiftOffset + tag.length() - 1;
}
public static class CommentTag {
final int fStartOffset;
final int fEndOffset;
final String fTextPart;
final boolean fIsSimpleTag;
CommentTag(int offset, int endOffset, String text) {
fStartOffset = offset;
fEndOffset = endOffset;
fIsSimpleTag = text != null && (text.trim().endsWith("/>")); //$NON-NLS-1$
fTextPart = text;
}
public int length() {
return fEndOffset - fStartOffset + 1;
}
public int offset() {
return fEndOffset;
}
@Override
public String toString() {
return fTextPart;
}
}
public static class Position {
/** The offset of the position */
private int offset;
/** The length of the position */
private int length;
/**
* Creates a new position with the given offset and length.
*
* @param offset the position offset, must be >= 0
* @param length the position length, must be >= 0
*/
public Position(int offset, int length) {
Assert.isTrue(offset >= 0);
Assert.isTrue(length >= 0);
this.offset = offset;
this.length = length;
}
@Override
public int hashCode() {
return (offset << 24) | (length << 16);
}
@Override
public boolean equals(Object other) {
if (other instanceof Position) {
Position rp= (Position) other;
return (rp.offset == offset) && (rp.length == length);
}
return super.equals(other);
}
@Override
public String toString() {
String position= "offset: " + offset + ", length: " + length; //$NON-NLS-1$//$NON-NLS-2$
return position;
}
}
}