/*******************************************************************************
* Copyright (c) 2009 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 v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Borland Software Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.compiler;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.m2m.internal.qvt.oml.NLS;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTBindingHelper;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTSyntheticNode;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTSyntheticNodeAccess;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.IModuleSourceInfo;
import org.eclipse.m2m.internal.qvt.oml.common.util.LineNumberProvider;
import org.eclipse.m2m.internal.qvt.oml.expressions.ExpressionsPackage;
import org.eclipse.m2m.internal.qvt.oml.expressions.Module;
import org.eclipse.ocl.utilities.ASTNode;
class DefaultExtensionDecoder implements ExeXMIExtensionEncoder {
private static final String OFFSET_DELIMITER = ","; //$NON-NLS-1$
private static final NodeHelper AST_HELPER = new ASTNodeHelper();
private static final NodeHelper AST_SYNTHETIC_HELPER = new SyntheticNodeHelper();
DefaultExtensionDecoder() {
super();
}
/* (non-Javadoc)
* @see org.eclipse.m2m.internal.qvt.oml.compiler.ExeXMIExtensionEncoder#decodeOffsets(org.eclipse.emf.ecore.resource.Resource, java.lang.String)
*/
public void decodeASTNodeOffsets(Resource res, String data) throws CorruptedExtensionData {
StringTokenizer tokenizer = new StringTokenizer(data, OFFSET_DELIMITER);
TreeIterator<EObject> nextRootContentsIt = res.getAllContents();
int prevStartPos = 0;
while (nextRootContentsIt.hasNext()) {
EObject next = nextRootContentsIt.next();
NodeHelper nodeHelper = getNodeHelper(next, false);
if(nodeHelper == null) {
continue;
}
int startPos = -1;
int endPos = -1;
for(int i = 0; i < 2; i++) {
int sign = 1;
if (tokenizer.hasMoreTokens()) {
String nextToken = tokenizer.nextToken();
char firstChar = nextToken.charAt(0);
if(firstChar == '-') {
// be fault tolerant to negative offsets (undefined by QVTO parsing)
nextToken = nextToken.substring(1);
sign = -1;
} else if(firstChar == '.') {
continue;
}
int nextInt;
try {
nextInt = (sign * Integer.parseInt(nextToken, 16)) + prevStartPos;
} catch(NumberFormatException e) {
throw new CorruptedExtensionData(NLS.bind("Invalid offset format ({0})", e.toString())); //$NON-NLS-1$
}
if(i == 0) {
startPos = nextInt;
prevStartPos = nextInt;
} else {
endPos = nextInt;
}
} else {
throw new CorruptedExtensionData("Missing offset data for existing AST Node"); //$NON-NLS-1$
}
}
if(startPos >= 0 && endPos >= 0) {
nodeHelper.setPositions(next, startPos, endPos);
}
}
if(tokenizer.hasMoreTokens()) {
throw new CorruptedExtensionData("Too many offsets data existing AST Nodes"); //$NON-NLS-1$
}
}
/* (non-Javadoc)
* @see org.eclipse.m2m.internal.qvt.oml.compiler.ExeXMIExtensionEncoder#encodeOffsets(org.eclipse.emf.ecore.resource.Resource)
*/
public String encodeASTNodeOffsets(Resource res) {
StringBuilder buf = new StringBuilder(1024);
int prevStartPos = 0;
int count = 0;
int[] startEndPositions = new int[2];
for(TreeIterator<EObject> nextRootContentsIt = res.getAllContents(); nextRootContentsIt.hasNext();) {
EObject next = nextRootContentsIt.next();
NodeHelper nodeHelper = getNodeHelper(next, true);
if(nodeHelper == null) {
continue;
}
if(count > 0) {
buf.append(OFFSET_DELIMITER);
}
nodeHelper.getPositions(next, startEndPositions);
int startPos = startEndPositions[0];
int endPos = startEndPositions[1];
if(startPos >= 0 && endPos >=0) {
int len = endPos - startPos;
int prevOffsetDelta = startPos - prevStartPos;
encodeInt(prevOffsetDelta, buf);
buf.append(OFFSET_DELIMITER);
encodeInt(len, buf);
prevStartPos = startPos;
} else {
// we do not case about meaningless settings but tolerate them ...
buf.append('.').append(OFFSET_DELIMITER).append('.');
}
count++;
}
return buf.toString();
}
/* (non-Javadoc)
* @see org.eclipse.m2m.internal.qvt.oml.compiler.ExeXMIExtensionEncoder#decodeLineBreakOffsets(java.lang.String)
*/
public int[] decodeLineBreakOffsets(String encodedOffsetData) throws CorruptedExtensionData {
StringTokenizer tokenizer = new StringTokenizer(encodedOffsetData, OFFSET_DELIMITER);
int[] result = null;
int i = 0;
int prevOffset = 0;
while(tokenizer.hasMoreTokens()) {
String nextToken = tokenizer.nextToken();
if(result == null) {
// read first integer as the count value
int count;
try {
count = Integer.parseInt(nextToken, 16);
} catch(NumberFormatException e) {
throw new CorruptedExtensionData(NLS.bind("Invalid offset format ({0})", e.toString())); //$NON-NLS-1$
}
result = new int[count];
continue;
}
int nextOffset = Integer.parseInt(nextToken, 16);
result[i++] = prevOffset + nextOffset;
prevOffset = nextOffset;
}
return result;
}
/* (non-Javadoc)
* @see org.eclipse.m2m.internal.qvt.oml.compiler.ExeXMIExtensionEncoder#encodeLineBreakOffsets(org.eclipse.emf.ecore.resource.Resource)
*/
public String encodeLineBreakOffsets(Resource res) {
int[] lineOffsets = null;
for (EObject nextRoot : res.getContents()) {
if(nextRoot instanceof Module) {
IModuleSourceInfo srcInfo = ASTBindingHelper.getModuleSourceBinding((Module) nextRoot);
if(srcInfo != null) {
LineNumberProvider lineNumProvider = srcInfo.getLineNumberProvider();
if(lineNumProvider instanceof BasicLineNumberProvider) {
BasicLineNumberProvider basicProvider = (BasicLineNumberProvider) lineNumProvider;
lineOffsets = basicProvider.lineBreakOffsets();
}
}
}
}
if(lineOffsets == null) {
return ""; //$NON-NLS-1$
}
StringBuilder buf = new StringBuilder(1024);
// write offset count first
buf.append(Integer.toHexString(lineOffsets.length)).append(OFFSET_DELIMITER);
int count = 0;
int prevOffset = 0;
for (int offset : lineOffsets) {
if(count > 0) {
buf.append(OFFSET_DELIMITER);
}
offset = offset - prevOffset;
buf.append(Integer.toHexString(offset));
prevOffset = offset;
count++;
}
return buf.toString();
}
private static NodeHelper getNodeHelper(EObject astElement, boolean encodingPhase) {
if(astElement instanceof ASTNode) {
return AST_HELPER;
} else if(synteticASTTypes.contains(astElement.eClass())) {
if(ASTSyntheticNodeAccess.getASTNode(astElement) == null) {
// FIXME - issue a warn TRACE if supported synthetic node type is not set during encoding
ASTSyntheticNodeAccess.createASTNode(astElement);
}
return AST_SYNTHETIC_HELPER;
}
return null;
}
private static final Set<EClass> synteticASTTypes = new HashSet<EClass>(Arrays.asList(
EcorePackage.eINSTANCE.getEAttribute(),
EcorePackage.eINSTANCE.getEReference(),
EcorePackage.eINSTANCE.getEClass(),
ExpressionsPackage.eINSTANCE.getLibrary(),
ExpressionsPackage.eINSTANCE.getOperationalTransformation(),
ExpressionsPackage.eINSTANCE.getContextualProperty()
));
private void encodeInt(int value, StringBuilder buf) {
if(value < 0) {
// specially encode negatives to avoid NFE from hex int parsing
buf.append('-');
value = -value;
}
buf.append(Integer.toHexString(value));
}
private interface NodeHelper {
Character getPrefixChar();
void setPositions(EObject node, int startPos, int endPos);
void getPositions(EObject node, int[] startEndPositions);
}
private static class SyntheticNodeHelper implements NodeHelper {
static final Character PREFIX = new Character('+');
public Character getPrefixChar() {
return PREFIX;
};
public void getPositions(EObject node, int[] startEndPositions) {
ASTSyntheticNode syntheticNode = ASTSyntheticNodeAccess.getASTNode(node);
startEndPositions[0] = syntheticNode.getStartPosition();
startEndPositions[1] = syntheticNode.getEndPosition();
}
public void setPositions(EObject node, int startPos, int endPos) {
ASTSyntheticNode syntheticNode = ASTSyntheticNodeAccess.getASTNode(node);
syntheticNode.setStartPosition(startPos);
syntheticNode.setEndPosition(endPos);
}
}
private static class ASTNodeHelper implements NodeHelper {
public Character getPrefixChar() {
return null;
}
public void getPositions(EObject node, int[] startEndPositions) {
ASTNode astNode = (ASTNode) node;
startEndPositions[0] = astNode.getStartPosition();
startEndPositions[1] = astNode.getEndPosition();
}
public void setPositions(EObject node, int startPos, int endPos) {
ASTNode astNode = (ASTNode) node;
astNode.setStartPosition(startPos);
astNode.setEndPosition(endPos);
}
}
}