blob: c779cbd161769e45e6fdf62251615a0006e6ae68 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2012 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.acceleo.model.mtl.resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.acceleo.common.utils.AcceleoASTNodeAdapter;
import org.eclipse.acceleo.model.mtl.Module;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.utilities.ASTNode;
/**
* EMTL/Binary Resource. The position of each AST node is serialized in a specific annotation. We compute the
* specific annotation content before saving the resource. We visit the annotation content after loading the
* resource to set the transient positions of every AST nodes.
*
* @author <a href="mailto:stephane.begaudeau@obeo.fr">Stephane Begaudeau</a>
* @since 3.1
*/
public class EMtlBinaryResourceImpl extends BinaryResourceImpl {
/**
* The specific annotation used to store the positions of the AST nodes.
*/
private static final String POSITIONS_ANNOTATION_NAME = "positions"; //$NON-NLS-1$
/** Holds the prefix of all temporary variables' names. */
private static final String VARIABLE_PREFIX = "temp"; //$NON-NLS-1$
/** This will hold all variables names when fixing ambiguities. */
private List<String> variableNames;
/**
* Indicates if the position should be trimmed.
*
* @since 3.2
*/
private boolean trimPosition;
/**
* Constructor.
*
* @param uri
* is the URI of the resource
*/
public EMtlBinaryResourceImpl(URI uri) {
super(uri);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl#doLoad(java.io.InputStream, java.util.Map)
*/
@Override
public void doLoad(InputStream inputStream, Map<?, ?> options) throws IOException {
Map<Object, Object> actualOptions = new HashMap<Object, Object>();
if (options != null) {
actualOptions.putAll(options);
}
super.doLoad(inputStream, actualOptions);
if (!trimPosition) {
EAnnotation positions = getPositions(false);
if (positions != null) {
restorePositions(positions);
getContents().remove(positions);
}
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl#doSave(java.io.OutputStream, java.util.Map)
*/
@Override
public void doSave(OutputStream outputStream, Map<?, ?> options) throws IOException {
// deactivate the notifications to work with EMF transaction
eSetDeliver(false);
EAnnotation positions = null;
if (!trimPosition) {
positions = getPositions(true);
fixVariablesAndPositions(positions);
}
try {
super.doSave(outputStream, options);
} finally {
if (!trimPosition && positions != null) {
getContents().remove(positions);
}
}
// re-activate the notifications
eSetDeliver(true);
}
/**
* Iterates over the whole content tree of the current resource and fixes {@link ASTNode}s' positions and
* temporary Variable names.
*
* @param positions
* is the annotation root element where to store the positions
*/
private void fixVariablesAndPositions(EAnnotation positions) {
variableNames = new ArrayList<String>();
Iterator<EObject> contentsIterator = getContents().iterator();
while (contentsIterator.hasNext()) {
EObject content = contentsIterator.next();
if (content instanceof Module) {
Module eModule = (Module)content;
TreeIterator<EObject> eAllContents = eModule.eAllContents();
while (eAllContents.hasNext()) {
EObject eObject = eAllContents.next();
fixVariableAmbiguities(eObject);
savePositions(eObject, positions);
}
}
}
variableNames.clear();
variableNames = null;
}
/**
* Alters names of temporary variables to unique names.
*
* @param element
* Element which name is to be changed if it is a temporary variable.
*/
private void fixVariableAmbiguities(EObject element) {
if (element instanceof Variable<?, ?> && ((Variable<?, ?>)element).getName() != null
&& ((Variable<?, ?>)element).getName().startsWith(VARIABLE_PREFIX)) {
final String varName = ((Variable<?, ?>)element).getName();
if (varName != null && !varName.matches("temp\\d+")) { //$NON-NLS-1$
return;
}
if (variableNames.contains(varName)) {
final String lastName = variableNames.get(variableNames.size() - 1);
int lastIndex = Integer.valueOf(lastName.substring(VARIABLE_PREFIX.length())).intValue();
do {
lastIndex++;
} while (variableNames.contains(VARIABLE_PREFIX + lastIndex));
((Variable<?, ?>)element).setName(VARIABLE_PREFIX + lastIndex);
variableNames.add(VARIABLE_PREFIX + lastIndex);
} else {
variableNames.add(varName);
}
}
}
/**
* Computes the specific annotation content before saving the resource. It will store every AST nodes
* positions.
*
* @param element
* Element which position is to be saved.
* @param positions
* is the annotation root element where to store the positions
*/
private void savePositions(EObject element, EAnnotation positions) {
if (element instanceof ASTNode) {
int start = ((ASTNode)element).getStartPosition();
int end = ((ASTNode)element).getEndPosition();
Adapter adapter = EcoreUtil.getAdapter(element.eAdapters(), AcceleoASTNodeAdapter.class);
int line = 0;
if (adapter instanceof AcceleoASTNodeAdapter) {
line = ((AcceleoASTNodeAdapter)adapter).getLine();
}
savePosition(positions, (ASTNode)element, start, end, line);
}
}
/**
* Computes the specific annotation content before saving the resource. It will store the given AST node
* position.
*
* @param positions
* is the annotation root element where to store the positions
* @param node
* the node to store
* @param start
* the real offset of the AST node
* @param end
* the last character position of the AST node
* @param line
* the number of the line on which this AST node starts.
*/
private void savePosition(EAnnotation positions, ASTNode node, int start, int end, int line) {
EAnnotation position = EcoreFactory.eINSTANCE.createEAnnotation();
position.setSource(POSITIONS_ANNOTATION_NAME + "." + positions.getEAnnotations().size()); //$NON-NLS-1$
position.getReferences().add(node);
position.getDetails().put("start", String.valueOf(start)); //$NON-NLS-1$
position.getDetails().put("end", String.valueOf(end)); //$NON-NLS-1$
position.getDetails().put("line", String.valueOf(line)); //$NON-NLS-1$
positions.getEAnnotations().add(position);
}
/**
* Visitor on the specific annotation content after loading the resource to set the transient positions of
* every AST nodes.
*
* @param positions
* is the annotation root element where to read the positions
*/
private void restorePositions(EAnnotation positions) {
Iterator<EAnnotation> positionsIt = positions.getEAnnotations().iterator();
while (positionsIt.hasNext()) {
EAnnotation position = positionsIt.next();
ASTNode node = (ASTNode)position.getReferences().get(0);
if (node.getStartPosition() == -1 && node.getEndPosition() == -1) {
try {
int start = Integer.parseInt(position.getDetails().get("start")); //$NON-NLS-1$
int end = Integer.parseInt(position.getDetails().get("end")); //$NON-NLS-1$
String lineValue = position.getDetails().get("line"); //$NON-NLS-1$
int line = 0;
if (lineValue != null) {
line = Integer.parseInt(lineValue);
}
node.setStartPosition(start);
node.setEndPosition(end);
node.eAdapters().add(new AcceleoASTNodeAdapter(line));
} catch (NumberFormatException e) {
// odd but not critical
}
}
}
}
/**
* Gets or creates the specific annotation used to store the positions of the AST nodes.
*
* @param create
* indicates if the annotation must be created
* @return the specific annotation
*/
private EAnnotation getPositions(boolean create) {
EAnnotation positions = null;
Iterator<EObject> children = getContents().iterator();
while (positions == null && children.hasNext()) {
EObject child = children.next();
if (child instanceof EAnnotation) {
if (POSITIONS_ANNOTATION_NAME.equals(((EAnnotation)child).getSource())) {
positions = (EAnnotation)child;
}
}
}
if (positions == null && create) {
positions = EcoreFactory.eINSTANCE.createEAnnotation();
positions.setSource(POSITIONS_ANNOTATION_NAME);
getContents().add(positions);
}
return positions;
}
/**
* Sets the boolean indicating if the position should be trimmed.
*
* @param trimPosition
* <code>true</code> to trim the position, <code>false</code> otherwise.
* @since 3.2
*/
public void setTrimPosition(boolean trimPosition) {
this.trimPosition = trimPosition;
}
}