| /******************************************************************************* |
| * Copyright (c) 2009, 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.internal.traceability.engine; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.acceleo.common.utils.CompactLinkedHashSet; |
| import org.eclipse.acceleo.common.utils.Deque; |
| import org.eclipse.acceleo.engine.AcceleoEngineMessages; |
| import org.eclipse.acceleo.engine.AcceleoEvaluationException; |
| import org.eclipse.acceleo.engine.internal.evaluation.AcceleoEvaluationVisitor; |
| import org.eclipse.acceleo.traceability.GeneratedFile; |
| import org.eclipse.acceleo.traceability.GeneratedText; |
| import org.eclipse.acceleo.traceability.InputElement; |
| import org.eclipse.acceleo.traceability.ModuleElement; |
| import org.eclipse.acceleo.traceability.TraceabilityFactory; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EOperation; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.ocl.ecore.CallOperationAction; |
| import org.eclipse.ocl.ecore.Constraint; |
| import org.eclipse.ocl.ecore.SendSignalAction; |
| |
| /** |
| * The purpose of this Utility class is to allow execution of the subset of Standard and non standard Acceleo |
| * operations, as well as the small subset of OCL operations that impacts traceability. |
| * |
| * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> |
| * @param <C> |
| * Will be either EClassifier for ecore or Classifier for UML. |
| * @param <PM> |
| * Will be either EParameter for ecore or Parameter for UML. |
| */ |
| public final class AcceleoTraceabilityOperationVisitor<C, PM> { |
| /** |
| * Maps a source String to its Tokenizer. Needed for the implementation of the standard operation |
| * "strtok(String, Integer)" as currently specified. |
| */ |
| private static final Map<String, TraceabilityTokenizer> TOKENIZERS = new HashMap<String, TraceabilityTokenizer>(); |
| |
| /** The evaluation visitor that spawned this operation visitor. */ |
| private AcceleoTraceabilityVisitor<EPackage, C, EOperation, EStructuralFeature, EEnumLiteral, PM, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> visitor; |
| |
| /** |
| * Instantiates an operation visitor given its parent evaluation visitor. |
| * |
| * @param visitor |
| * Our parent evaluation visitor. |
| */ |
| public AcceleoTraceabilityOperationVisitor( |
| AcceleoTraceabilityVisitor<EPackage, C, EOperation, EStructuralFeature, EEnumLiteral, PM, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> visitor) { |
| this.visitor = visitor; |
| } |
| |
| /** |
| * Handles the "Collection::first" OCL operation directly from the traceability visitor as we need to |
| * alter recorded traceability information. |
| * |
| * @param <T> |
| * Type of the collection's content. |
| * @param source |
| * Collection from which to retrieve the first element. |
| * @return First element of the collection. Traceability information will have been changed directly |
| * within {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public <T> T visitFirstOperation(Collection<T> source) { |
| if (source.isEmpty()) { |
| return null; |
| } |
| T result = source.iterator().next(); |
| |
| if (TraceabilityVisitorUtil.isPrimitive(result)) { |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| |
| /* |
| * Retrieve the list of all regions, regardless of their input. We'll later use this list to know |
| * which regions we should retain. |
| */ |
| List<GeneratedText> regions = new ArrayList<GeneratedText>(); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| regions.addAll(entry.getValue()); |
| } |
| |
| // If we're lucky, there is a single region, otherwise : |
| if (regions.size() > 1) { |
| // Sort them so we don't need to worry about their order |
| Collections.sort(regions); |
| |
| if (regions.size() == source.size()) { |
| // If there is one region per value of the source, all the better |
| regions = regions.subList(0, 1); |
| } else { |
| /* |
| * Otherwise, we'll have to assume the regions correspond to the value's toString() -Note |
| * that we'll never be here for non-primitive Collections- |
| */ |
| int valueLength = result.toString().length(); |
| |
| int regionsStartIndex = 0; |
| GeneratedText region = regions.get(regionsStartIndex); |
| int regionLength = region.getEndOffset() - region.getStartOffset(); |
| |
| // skip all the way ahead to the first region for this value |
| int regionsEndIndex = regionsStartIndex; |
| int gap = valueLength - regionLength; |
| while (gap > 0) { |
| regionsEndIndex++; |
| GeneratedText next = regions.get(regionsEndIndex); |
| int nextLength = next.getEndOffset() - next.getStartOffset(); |
| gap = gap - nextLength; |
| } |
| |
| regions = regions.subList(regionsStartIndex, regionsEndIndex + 1); |
| } |
| |
| Iterator<Map.Entry<InputElement, Set<GeneratedText>>> entryIterator = trace.getTraces() |
| .entrySet().iterator(); |
| while (entryIterator.hasNext()) { |
| Map.Entry<InputElement, Set<GeneratedText>> entry = entryIterator.next(); |
| entry.getValue().retainAll(regions); |
| if (entry.getValue().isEmpty()) { |
| entryIterator.remove(); |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Handles the "String::first" OCL operation directly from the traceability visitor as we need to alter |
| * recorded traceability information. |
| * |
| * @param source |
| * String from which to take out a substring. |
| * @param endIndex |
| * Index at which the substring ends. |
| * @return The substring. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitFirstOperation(String source, int endIndex) { |
| final String result; |
| if (endIndex < 0 || endIndex > source.length()) { |
| result = source; |
| } else { |
| result = visitSubstringOperation(source, 0, endIndex); |
| } |
| return result; |
| } |
| |
| /** |
| * Handles the "Collection::last" OCL operation directly from the traceability visitor as we need to alter |
| * recorded traceability information. |
| * |
| * @param <T> |
| * Type of the collection's content. |
| * @param source |
| * Collection from which to retrieve the last element. |
| * @return Last element of the collection. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public <T> T visitLastOperation(Collection<T> source) { |
| if (source.isEmpty()) { |
| return null; |
| } |
| |
| T result; |
| if (source instanceof List<?>) { |
| result = ((List<T>)source).listIterator(source.size()).previous(); |
| } else { |
| Iterator<T> valueIterator = source.iterator(); |
| do { |
| result = valueIterator.next(); |
| } while (valueIterator.hasNext()); |
| } |
| |
| if (TraceabilityVisitorUtil.isPrimitive(result)) { |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| |
| /* |
| * Retrieve the list of all regions, regardless of their input. We'll later use this list to know |
| * which regions we should retain. |
| */ |
| List<GeneratedText> regions = new ArrayList<GeneratedText>(); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| regions.addAll(entry.getValue()); |
| } |
| |
| // If we're lucky, there is a single region, otherwise : |
| if (regions.size() > 1) { |
| // Sort them so we don't need to worry about their order |
| Collections.sort(regions); |
| |
| /* |
| * Since we'll remove all regions except for the last, we need to remember the first of their |
| * offsets in order to compensate the gap. |
| */ |
| int regionsStartOffset = regions.get(0).getStartOffset(); |
| |
| if (regions.size() == source.size()) { |
| // If there is one region per value of the source, all the better |
| regions = regions.subList(regions.size() - 1, regions.size()); |
| } else { |
| /* |
| * Otherwise, we'll have to assume the regions correspond to the value's toString() -Note |
| * that we'll never be here for non-primitive Collections- |
| */ |
| int valueLength = result.toString().length(); |
| |
| int regionsEndIndex = regions.size() - 1; |
| GeneratedText region = regions.get(regionsEndIndex); |
| int regionLength = region.getEndOffset() - region.getStartOffset(); |
| |
| // go all the way back to the first region for this value |
| int regionsStartIndex = regionsEndIndex; |
| int gap = valueLength - regionLength; |
| while (gap > 0) { |
| regionsStartIndex--; |
| GeneratedText previous = regions.get(regionsStartIndex); |
| int previousLength = previous.getEndOffset() - previous.getStartOffset(); |
| gap = gap - previousLength; |
| } |
| |
| regions = regions.subList(regionsStartIndex, regionsEndIndex + 1); |
| } |
| |
| // Now that we know which regions to retain, restore their offsets |
| for (GeneratedText region : regions) { |
| int regionLength = region.getEndOffset() - region.getStartOffset(); |
| region.setStartOffset(regionsStartOffset); |
| region.setEndOffset(regionsStartOffset + regionLength); |
| regionsStartOffset += regionLength; |
| } |
| |
| Iterator<Map.Entry<InputElement, Set<GeneratedText>>> entryIterator = trace.getTraces() |
| .entrySet().iterator(); |
| while (entryIterator.hasNext()) { |
| Map.Entry<InputElement, Set<GeneratedText>> entry = entryIterator.next(); |
| entry.getValue().retainAll(regions); |
| if (entry.getValue().isEmpty()) { |
| entryIterator.remove(); |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Handles the "String::last" OCL operation directly from the traceability visitor as we need to alter |
| * recorded traceability information. |
| * |
| * @param source |
| * String from which to take out a substring. |
| * @param charCount |
| * Number of characters to keep. |
| * @return The substring. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitLastOperation(String source, int charCount) { |
| final String result; |
| if (charCount < 0 || charCount > source.length()) { |
| result = source; |
| } else { |
| result = visitSubstringOperation(source, source.length() - charCount, source.length()); |
| } |
| return result; |
| } |
| |
| /** |
| * Handles the "substitute" and "replace" standard operations directly from the traceability visitor so as |
| * to record accurate traceability information. |
| * |
| * @param source |
| * String within which we need to replace substrings. |
| * @param substring |
| * The substring that is to be substituted. |
| * @param replacement |
| * The String which will be inserted in <em>source</em> where <em>substring</em> was. |
| * @param substitutionTrace |
| * Traceability information of the replacement. |
| * @param substituteAll |
| * Indicates wheter we should substitute all occurences of the substring or only the first. |
| * @param useInvocationTrace |
| * If <code>true</code>, {@link #invocationTraces} will be altered in place of the last |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. Should only be <code>true</code> when |
| * altering indentation. |
| * @return <em>source</em> with the first occurence of <em>substring</em> replaced by <em>replacement</em> |
| * or <em>source</em> unchanged if it did not contain <em>substring</em>. |
| */ |
| public String visitReplaceOperation(String source, String substring, String replacement, |
| ExpressionTrace<C> substitutionTrace, boolean substituteAll, boolean useInvocationTrace) { |
| if (substring == null || replacement == null) { |
| throw new NullPointerException(); |
| } |
| |
| Matcher sourceMatcher = Pattern.compile(substring).matcher(source); |
| StringBuffer result = new StringBuffer(); |
| boolean hasMatch = sourceMatcher.find(); |
| // Note : despite its name, this could be negative |
| int addedLength = 0; |
| |
| // protected area indentation markers must be ignored |
| final boolean hasProtectedMarker = source.contains(AcceleoEvaluationVisitor.PROTECTED_AREA_MARKER); |
| |
| // FIXME This loop does _not_ take group references into account except "$0 at the start" |
| final boolean startsWithZeroGroupRef = replacement.startsWith("$0"); //$NON-NLS-1$ |
| while (hasMatch) { |
| // If we've already changed the String size, take it into account |
| int startIndex = sourceMatcher.start() + addedLength; |
| int endIndex = sourceMatcher.end() + addedLength; |
| if (startsWithZeroGroupRef) { |
| startIndex = endIndex; |
| } |
| sourceMatcher.appendReplacement(result, replacement); |
| int replacementLength = result.length() - startIndex; |
| // We now remove from the replacementLength the length of the replaced substring |
| replacementLength -= endIndex - startIndex; |
| addedLength += replacementLength; |
| |
| // Note that we could be out of a file block's scope |
| if (useInvocationTrace && visitor.getCurrentFiles().size() > 0) { |
| // We need the starting index of these traces |
| int offsetGap = -1; |
| for (ExpressionTrace<C> trace : visitor.getInvocationTraces()) { |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| for (GeneratedText text : entry.getValue()) { |
| if (offsetGap == -1 || text.getStartOffset() < offsetGap) { |
| offsetGap = text.getStartOffset(); |
| } |
| } |
| } |
| } |
| startIndex += offsetGap; |
| endIndex += offsetGap; |
| |
| if (hasProtectedMarker) { |
| int additionalGap = computeAdditionalGap(source, sourceMatcher.start()); |
| startIndex += additionalGap; |
| endIndex += additionalGap; |
| } |
| |
| for (ExpressionTrace<C> trace : visitor.getInvocationTraces()) { |
| changeTraceabilityIndicesOfReplaceOperation(trace, startIndex, endIndex, |
| replacementLength); |
| } |
| GeneratedFile generatedFile = visitor.getCurrentFiles().getLast(); |
| final int fileLength = generatedFile.getLength(); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : substitutionTrace.getTraces() |
| .entrySet()) { |
| for (GeneratedText text : entry.getValue()) { |
| // 3.4 compatibility : EcoreUtil.copy() wasn't generic. The cast is necessary |
| @SuppressWarnings("cast") |
| GeneratedText copy = (GeneratedText)EcoreUtil.copy(text); |
| copy.setStartOffset(copy.getStartOffset() + startIndex); |
| copy.setEndOffset(copy.getEndOffset() + startIndex); |
| generatedFile.getGeneratedRegions().add(copy); |
| Iterator<ExpressionTrace<C>> traceIterator = visitor.getInvocationTraces().iterator(); |
| boolean inserted = false; |
| while (traceIterator.hasNext() && !inserted) { |
| inserted = insertTextInTrace(traceIterator.next(), copy); |
| } |
| } |
| } |
| generatedFile.setLength(fileLength + replacementLength); |
| } else { |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| changeTraceabilityIndicesOfReplaceOperation(trace, startIndex, endIndex, replacementLength); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : substitutionTrace.getTraces() |
| .entrySet()) { |
| Set<GeneratedText> existingTraces = trace.getTraces().get(entry.getKey()); |
| if (existingTraces == null) { |
| existingTraces = new CompactLinkedHashSet<GeneratedText>(); |
| trace.getTraces().put(entry.getKey(), existingTraces); |
| } |
| for (GeneratedText text : entry.getValue()) { |
| // 3.4 compatibility : EcoreUtil.copy() wasn't generic. The cast is necessary |
| @SuppressWarnings("cast") |
| GeneratedText copy = (GeneratedText)EcoreUtil.copy(text); |
| copy.setStartOffset(copy.getStartOffset() + startIndex); |
| copy.setEndOffset(copy.getEndOffset() + startIndex); |
| existingTraces.add(copy); |
| } |
| } |
| } |
| |
| if (!substituteAll) { |
| // Do once |
| break; |
| } |
| hasMatch = sourceMatcher.find(); |
| } |
| sourceMatcher.appendTail(result); |
| return result.toString(); |
| } |
| |
| /** |
| * Takes care of the "reverse" non standard operations from the traceability visitor so as to properly |
| * reverse traceability information. |
| * |
| * @param source |
| * Collection we need to reverse. |
| * @return The reversed collection. |
| */ |
| public Collection<Object> visitReverseOperation(Collection<Object> source) { |
| Collection<Object> result; |
| final List<Object> temp = new ArrayList<Object>(source); |
| Collections.reverse(temp); |
| if (source instanceof LinkedHashSet<?>) { |
| final Set<Object> reversedSet = new CompactLinkedHashSet<Object>(temp); |
| result = reversedSet; |
| } else { |
| result = temp; |
| } |
| |
| // Only alter the traceability information if the collection is primitive |
| if (TraceabilityVisitorUtil.isPrimitive(source)) { |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| |
| // Retrieve the list of all regions, regardless of their input |
| List<GeneratedText> regions = new ArrayList<GeneratedText>(); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| regions.addAll(entry.getValue()); |
| } |
| |
| // Sort them so that we don't have to worry about their order |
| Collections.sort(regions); |
| |
| int regionsLength = 0; |
| if (regions.size() == source.size()) { |
| // If there is one generated region for each source value, all the better |
| for (int i = regions.size() - 1; i >= 0; i--) { |
| GeneratedText region = regions.get(i); |
| int startOffset = regionsLength; |
| regionsLength += region.getEndOffset() - region.getStartOffset(); |
| region.setStartOffset(startOffset); |
| region.setEndOffset(regionsLength); |
| } |
| } else { |
| /* |
| * Otherwise, we'll have to assume the regions correspond to the value's toString() -Note that |
| * we'll never be here for non-primitive Collections- |
| */ |
| List<Integer> valueLengthes = new ArrayList<Integer>(source.size()); |
| for (Object value : source) { |
| String stringValue = value.toString(); |
| valueLengthes.add(Integer.valueOf(stringValue.length())); |
| } |
| Collections.reverse(valueLengthes); |
| |
| for (int i = valueLengthes.size() - 1; i >= 0; i--) { |
| int valueLength = valueLengthes.get(i).intValue(); |
| GeneratedText region = regions.get(i); |
| int regionLength = region.getEndOffset() - region.getStartOffset(); |
| |
| // go back all the way back to the first region for this value |
| int valueRegionsStartIndex = i; |
| int gap = valueLength - regionLength; |
| while (gap > 0) { |
| valueRegionsStartIndex--; |
| GeneratedText previous = regions.get(valueRegionsStartIndex); |
| int previousLength = previous.getEndOffset() - previous.getStartOffset(); |
| gap = gap - previousLength; |
| } |
| |
| // We found the first region for this value. start reversing it |
| for (int j = valueRegionsStartIndex; j < regions.size(); j++) { |
| GeneratedText text = regions.get(j); |
| |
| int startOffset = regionsLength; |
| regionsLength += text.getEndOffset() - text.getEndOffset(); |
| |
| region.setStartOffset(startOffset); |
| region.setEndOffset(regionsLength); |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Takes care of the "sep" non standard operations from the traceability visitor so as to track |
| * traceability information for the added separators. |
| * |
| * @param source |
| * Collection in which we need to add the separators. |
| * @param separator |
| * Separator that is to be added in-between elements. |
| * @param separatorTrace |
| * Traceability information of the separator. |
| * @return The source collection, with <em>separator</em> inserted in-between each couple of elements from |
| * the <em>source</em>. |
| */ |
| public Collection<Object> visitSepOperation(Collection<Object> source, String separator, |
| ExpressionTrace<C> separatorTrace) { |
| final Collection<Object> temp = new ArrayList<Object>(source.size() << 1); |
| final List<String> stringSource = new ArrayList<String>(); |
| final Iterator<?> sourceIterator = source.iterator(); |
| while (sourceIterator.hasNext()) { |
| Object nextSource = sourceIterator.next(); |
| temp.add(nextSource); |
| stringSource.add(nextSource.toString()); |
| if (sourceIterator.hasNext()) { |
| temp.add(separator); |
| } |
| } |
| /* |
| * We'll assume all elements of the collection are Strings or will be printed as such, this should |
| * handle most cases. |
| */ |
| final int separatorLength = separator.length(); |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| int currentSeparatorOffset = 0; |
| for (int i = 0; i < stringSource.size() - 1; i++) { |
| String element = stringSource.get(i); |
| currentSeparatorOffset += element.length(); |
| |
| // All traces starting after this offset must be switched by 'separatorLength' |
| final Iterator<Map.Entry<InputElement, Set<GeneratedText>>> entryIterator = trace.getTraces() |
| .entrySet().iterator(); |
| while (entryIterator.hasNext()) { |
| final Iterator<GeneratedText> textIterator = entryIterator.next().getValue().iterator(); |
| while (textIterator.hasNext()) { |
| GeneratedText text = textIterator.next(); |
| if (text.getStartOffset() >= currentSeparatorOffset) { |
| text.setStartOffset(text.getStartOffset() + separatorLength); |
| text.setEndOffset(text.getEndOffset() + separatorLength); |
| } |
| } |
| } |
| |
| // Finally, Insert the separator region in the trace |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : separatorTrace.getTraces().entrySet()) { |
| Set<GeneratedText> existingTraces = trace.getTraces().get(entry.getKey()); |
| if (existingTraces == null) { |
| existingTraces = new CompactLinkedHashSet<GeneratedText>(); |
| trace.getTraces().put(entry.getKey(), existingTraces); |
| } |
| for (GeneratedText text : entry.getValue()) { |
| // 3.4 compatibility : EcoreUtil.copy() wasn't generic. The cast is necessary |
| @SuppressWarnings("cast") |
| GeneratedText copy = (GeneratedText)EcoreUtil.copy(text); |
| copy.setStartOffset(copy.getStartOffset() + currentSeparatorOffset); |
| copy.setEndOffset(copy.getEndOffset() + currentSeparatorOffset); |
| existingTraces.add(copy); |
| } |
| } |
| |
| // Don't forget that the next "separatorOffset" can only start after the current |
| currentSeparatorOffset += separatorLength; |
| } |
| |
| return temp; |
| } |
| |
| /** |
| * Handles the "strtok" standard operation directly from the traceability visitor as we need to alter |
| * recorded traceability information. |
| * |
| * @param source |
| * Source String in which tokenization has to take place. |
| * @param delimiters |
| * Delimiters around which the <code>source</code> has to be split. |
| * @param flag |
| * The value of this influences the token that needs be returned. A value of <code>0</code> |
| * means the very first token will be returned, a value of <code>1</code> means the returned |
| * token will be the next (as compared to the last one that's been returned). |
| * @return The first of all tokens if <code>flag</code> is <code>0</code>, the next token if |
| * <code>flag</code> is <code>1</code>. Fails in {@link AcceleoEvaluationException} otherwise. |
| * @see org.eclipse.acceleo.engine.internal.environment.AcceleoLibraryOperationVisitor#strtok(String, |
| * String, Integer) |
| */ |
| public String visitStrtokOperation(String source, String delimiters, Integer flag) { |
| final String result; |
| final TraceabilityTokenizer tokenizer; |
| if (flag.intValue() == 0) { |
| tokenizer = new TraceabilityTokenizer(source, delimiters); |
| TOKENIZERS.put(source, tokenizer); |
| result = tokenizer.nextToken(); |
| } else if (flag.intValue() == 1) { |
| if (TOKENIZERS.containsKey(source)) { |
| tokenizer = TOKENIZERS.get(source); |
| } else { |
| tokenizer = new TraceabilityTokenizer(source, delimiters); |
| TOKENIZERS.put(source, tokenizer); |
| } |
| String token = ""; //$NON-NLS-1$ |
| if (tokenizer.hasMoreTokens()) { |
| token = tokenizer.nextToken(); |
| } |
| result = token; |
| } else { |
| throw new AcceleoEvaluationException(AcceleoEngineMessages.getString( |
| "AcceleoEvaluationEnvironment.IllegalTokenizerFlag", flag)); //$NON-NLS-1$ |
| } |
| |
| changeTraceabilityIndicesSubstringReturn(tokenizer.getLastOffset(), tokenizer.getNextOffset()); |
| |
| return result; |
| } |
| |
| /** |
| * Handles the "substring" non standard operation directly from the traceability visitor as we need to |
| * alter recorded traceability information. |
| * |
| * @param source |
| * String from which to take out a substring. |
| * @param startIndex |
| * Index at which the substring starts. |
| * @return The substring. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitSubstringOperation(String source, int startIndex) { |
| changeTraceabilityIndicesSubstringReturn(startIndex, source.length()); |
| |
| return source.substring(startIndex); |
| } |
| |
| /** |
| * Handles the "substring" OCL operation directly from the traceability visitor as we need to alter |
| * recorded traceability information. |
| * |
| * @param source |
| * String from which to take out a substring. |
| * @param startIndex |
| * Index at which the substring starts. |
| * @param endIndex |
| * Index at which the substring ends. |
| * @return The substring. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitSubstringOperation(String source, int startIndex, int endIndex) { |
| changeTraceabilityIndicesSubstringReturn(startIndex, endIndex); |
| |
| return source.substring(startIndex, endIndex); |
| } |
| |
| /** |
| * Handles the "tokenize" non-standard operation directly from the traceability visitor as we need to |
| * alter recorded traceability information. |
| * |
| * @param source |
| * String that is to be tokenized. |
| * @param delims |
| * Delimiters to tokenize around. |
| * @return The List of tokens <code>source</code> contains. Traceability information will have been |
| * changed directly within {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public List<String> visitTokenizeOperation(String source, String delims) { |
| final TraceabilityTokenizer tokenizer = new TraceabilityTokenizer(source, delims); |
| final List<String> result = new ArrayList<String>(); |
| final AbstractTrace trace = visitor.getLastExpressionTrace(); |
| |
| // We need the starting index of these traces |
| int offsetGap = -1; |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| for (GeneratedText text : entry.getValue()) { |
| if (offsetGap == -1 || text.getStartOffset() < offsetGap) { |
| offsetGap = text.getStartOffset(); |
| } |
| } |
| } |
| |
| int[][] tokenRegions = new int[tokenizer.countTokens()][2]; |
| |
| int tokenCount = 0; |
| while (tokenizer.hasMoreTokens()) { |
| result.add(tokenizer.nextToken()); |
| |
| int startIndex = tokenizer.getLastOffset(); |
| int endIndex = tokenizer.getNextOffset(); |
| |
| tokenRegions[tokenCount][0] = startIndex + offsetGap; |
| tokenRegions[tokenCount][1] = endIndex + offsetGap; |
| tokenCount++; |
| } |
| |
| // First of all, if we have but a single token, this acts as substring does |
| if (result.size() == 1) { |
| changeTraceabilityIndicesSubstringReturn(tokenRegions[0][0], tokenRegions[0][1]); |
| } else { |
| Set<Map.Entry<InputElement, Set<GeneratedText>>> traceRegions = trace.getTraces().entrySet(); |
| // If we only have a single region, we only need to split it |
| if (trace.getTraces().size() == 1 && traceRegions.iterator().next().getValue().size() == 1) { |
| Map.Entry<InputElement, Set<GeneratedText>> entry = traceRegions.iterator().next(); |
| Set<GeneratedText> generatedRegions = entry.getValue(); |
| GeneratedText firstRegion = generatedRegions.iterator().next(); |
| int length = tokenRegions[0][1] - tokenRegions[0][0]; |
| firstRegion.setStartOffset(0); |
| firstRegion.setEndOffset(length); |
| |
| for (int i = 1; i < tokenRegions.length; i++) { |
| int start = tokenRegions[i][0]; |
| int end = tokenRegions[i][1]; |
| int startOffset = length; |
| length += end - start; |
| |
| // 3.4 compatibility : EcoreUtil.copy() wasn't generic. The cast is necessary |
| @SuppressWarnings("cast") |
| GeneratedText newRegion = (GeneratedText)EcoreUtil.copy(firstRegion); |
| newRegion.setStartOffset(startOffset); |
| newRegion.setEndOffset(length); |
| |
| generatedRegions.add(newRegion); |
| } |
| } else { |
| // Otherwise, we need to find back from which generated region a given token has been taken |
| changeTraceabilityIndicesComplexTokenize(trace, tokenRegions); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Handles the "tostring" non-standard operation for non-primitives directly from the traceability visitor |
| * as we need to create traceability information. |
| * |
| * @param source |
| * Object we need the String value of. |
| * @param moduleElement |
| * The module element that triggered this toString call. |
| * @return The String value of the <code>source</code>. Traceability information will have been changed |
| * directly within {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitTostringOperation(Object source, ModuleElement moduleElement) { |
| final StringBuffer buffer = new StringBuffer(); |
| if (source instanceof Collection<?>) { |
| final Iterator<?> childrenIterator = ((Collection<?>)source).iterator(); |
| while (childrenIterator.hasNext()) { |
| buffer.append(visitTostringOperation(childrenIterator.next(), moduleElement)); |
| } |
| } else if (source != null) { |
| String result = source.toString(); |
| buffer.append(result); |
| |
| final EObject scope; |
| if (source instanceof EObject) { |
| scope = (EObject)source; |
| } else { |
| scope = visitor.retrieveScopeEObjectValue(); |
| } |
| if (scope != null) { |
| final AbstractTrace trace = visitor.getLastExpressionTrace(); |
| GeneratedText region = TraceabilityFactory.eINSTANCE.createGeneratedText(); |
| |
| region.setModuleElement(moduleElement); |
| |
| trace.addTrace(visitor.getInputElement(scope), region, result); |
| } |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Handles the "trim" non-standard operation directly from the traceability visitor as we need to alter |
| * recorded traceability information. |
| * |
| * @param source |
| * String that is to be trimmed. |
| * @return The trimmed String. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitTrimOperation(String source) { |
| return visitTrimOperation(source, 0); |
| } |
| |
| /** |
| * This will be used to trim the protected area markers. |
| * |
| * @param source |
| * String that is to be trimmed. |
| * @param gap |
| * The marker might begin at an index superior to 0. |
| * @return The trimmed String. Traceability information will have been changed directly within |
| * {@link AcceleoTraceabilityVisitor#recordedTraces}. |
| */ |
| public String visitTrimOperation(String source, int gap) { |
| int start = 0; |
| int end = source.length(); |
| char[] chars = source.toCharArray(); |
| while (start < end && chars[start] <= ' ') { |
| start++; |
| } |
| while (start < end && chars[end - 1] <= ' ') { |
| end--; |
| } |
| |
| start += gap; |
| end += gap; |
| changeTraceabilityIndicesSubstringReturn(start, end); |
| |
| return source.trim(); |
| } |
| |
| /** |
| * For non-primitives, we'll need to create our own traceability information. <code>moduleElement</code> |
| * will then hold a reference to the module element that triggered the operation call. |
| * |
| * @param result |
| * Result of the operation call. |
| * @param source |
| * Source of the operation call. |
| * @param moduleElement |
| * The module element that triggered this toString call. |
| */ |
| void changeTraceabilityIndicesBooleanReturn(boolean result, Object source, ModuleElement moduleElement) { |
| if (!visitor.getLastExpressionTrace().getTraces().isEmpty()) { |
| changeTraceabilityIndicesBooleanReturn(result); |
| } else if (source instanceof EObject) { |
| final AbstractTrace trace = visitor.getLastExpressionTrace(); |
| GeneratedText region = TraceabilityFactory.eINSTANCE.createGeneratedText(); |
| |
| region.setModuleElement(moduleElement); |
| |
| final int length; |
| if (result) { |
| length = 4; |
| } else { |
| length = 5; |
| } |
| trace.addTrace(visitor.getInputElement((EObject)source), region, length); |
| } |
| } |
| |
| /** |
| * For non-primitives, we'll need to create our own traceability information. <code>moduleElement</code> |
| * will then hold a reference to the module element that triggered the operation call. |
| * |
| * @param result |
| * Result of the operation call. |
| * @param source |
| * Source of the operation call. |
| * @param moduleElement |
| * The module element that triggered this toString call. |
| * @see #changeTraceabilityIndicesNumberReturn(Number) |
| */ |
| void changeTraceabilityIndicesNumberReturn(Number result, Object source, ModuleElement moduleElement) { |
| if (!visitor.getLastExpressionTrace().getTraces().isEmpty()) { |
| changeTraceabilityIndicesNumberReturn(result); |
| } else if (source instanceof EObject) { |
| final AbstractTrace trace = visitor.getLastExpressionTrace(); |
| GeneratedText region = TraceabilityFactory.eINSTANCE.createGeneratedText(); |
| |
| region.setModuleElement(moduleElement); |
| |
| final int length = String.valueOf(result).length(); |
| trace.addTrace(visitor.getInputElement((EObject)source), region, length); |
| } |
| } |
| |
| /** |
| * isAlpha, isAlphanum and possibly other traceability impacting operations share the same "basic" |
| * behavior of altering indices to reflect a boolean return. This behavior is externalized here. |
| * |
| * @param result |
| * Boolean result of the operation. We'll only leave a four-characters long region if |
| * <code>true</code>, five-characters long if <code>false</code>. |
| */ |
| private void changeTraceabilityIndicesBooleanReturn(boolean result) { |
| // We'll keep only the very last trace and alter its indices |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| Map.Entry<InputElement, Set<GeneratedText>> lastEntry = null; |
| GeneratedText lastRegion = null; |
| List<GeneratedText> removeRegions = new ArrayList<GeneratedText>(); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| Iterator<GeneratedText> textIterator = entry.getValue().iterator(); |
| while (textIterator.hasNext()) { |
| GeneratedText text = textIterator.next(); |
| if (lastRegion == null) { |
| lastRegion = text; |
| lastEntry = entry; |
| } else if (text.getEndOffset() > lastRegion.getEndOffset()) { |
| // lastEntry cannot be null once we get here |
| assert lastEntry != null; |
| removeRegions.add(lastRegion); |
| if (entry != lastEntry) { |
| lastEntry.getValue().removeAll(removeRegions); |
| removeRegions.clear(); |
| } |
| lastRegion = text; |
| lastEntry = entry; |
| } else { |
| textIterator.remove(); |
| } |
| } |
| } |
| final int length; |
| if (result) { |
| length = 4; |
| } else { |
| length = 5; |
| } |
| if (lastRegion != null) { |
| lastRegion.setStartOffset(0); |
| lastRegion.setEndOffset(length); |
| } |
| trace.setOffset(length); |
| } |
| |
| /** |
| * This will alter the traceability information for complex tokenizations. |
| * <p> |
| * Specifically, this is aimed at altering traces for tokenization where tokens could be spanned accross |
| * multiple generated regions. For example, |
| * |
| * <pre> |
| * ('arai' + 'bza' + 'geaa' + 'ab' + 'adaa') |
| * </pre> |
| * |
| * Creates 5 generated regions : [ [0,4], [4,7], [7,11], [11,13], [13,17] ] while |
| * |
| * <pre> |
| * ('arai' + 'bza' + 'geaa' + 'ab' + 'adaa').tokenize('a') |
| * </pre> |
| * |
| * Creates 6 generated regions : [ [0,1], [1,2], [2, 4], [4,6], [6,7], [7,8] ] as the second token ("ibz") |
| * spans accross both the generated regions for the string literals 'arai' and 'bza'. |
| * </p> |
| * |
| * @param trace |
| * The trace we are to alter. |
| * @param tokenRegions |
| * offsets of the token regions. |
| */ |
| // 3.4 compatibility : EcoreUtil.copy() wasn't generic. The cast is necessary |
| @SuppressWarnings("cast") |
| private void changeTraceabilityIndicesComplexTokenize(AbstractTrace trace, int[][] tokenRegions) { |
| /* |
| * Copy the map : none of the old generated regions will be kept : we'll replace all of them with the |
| * token regions. |
| */ |
| Map<InputElement, Set<GeneratedText>> traceCopy = new HashMap<InputElement, Set<GeneratedText>>(trace |
| .getTraces()); |
| trace.getTraces().clear(); |
| |
| int length = 0; |
| for (int[] tokenRegion : tokenRegions) { |
| int tokenStart = tokenRegion[0]; |
| int tokenEnd = tokenRegion[1]; |
| int tokenLength = tokenEnd - tokenStart; |
| |
| // We'll try and break our loops as soon as the whole token as been created |
| int expectedLength = length + tokenLength; |
| |
| Iterator<Map.Entry<InputElement, Set<GeneratedText>>> entryIterator = traceCopy.entrySet() |
| .iterator(); |
| while (entryIterator.hasNext() && length < expectedLength) { |
| Map.Entry<InputElement, Set<GeneratedText>> entry = entryIterator.next(); |
| Iterator<GeneratedText> textIterator = entry.getValue().iterator(); |
| while (textIterator.hasNext() && length < expectedLength) { |
| GeneratedText text = textIterator.next(); |
| |
| GeneratedText tokenText = null; |
| |
| /* |
| * We can be in one of four cases : The token is fully included into a region, the region |
| * is fully included in a token, the region ends with a token, or the region starts with a |
| * token. |
| */ |
| if (text.getStartOffset() <= tokenStart && text.getEndOffset() >= tokenEnd) { |
| // token fully included in generated region |
| tokenText = TraceabilityFactory.eINSTANCE.createGeneratedText(); |
| tokenText.setStartOffset(length); |
| tokenText.setEndOffset(length + tokenLength); |
| tokenText.setModuleElement(text.getModuleElement()); |
| tokenText.setOutputFile(text.getOutputFile()); |
| tokenText.setSourceElement(text.getSourceElement()); |
| |
| length += tokenLength; |
| } else if (text.getStartOffset() >= tokenStart && text.getEndOffset() <= tokenEnd) { |
| // generated region fully included in token |
| int textLength = text.getEndOffset() - text.getStartOffset(); |
| tokenText = (GeneratedText)EcoreUtil.copy(text); |
| tokenText.setStartOffset(length); |
| tokenText.setEndOffset(length + textLength); |
| |
| length += textLength; |
| } else if (text.getStartOffset() <= tokenStart && text.getEndOffset() > tokenStart |
| && text.getEndOffset() < tokenEnd) { |
| // generated region ends with token |
| int tokenRegionLength = text.getEndOffset() - tokenStart; |
| tokenText = TraceabilityFactory.eINSTANCE.createGeneratedText(); |
| tokenText.setStartOffset(length); |
| tokenText.setEndOffset(length + tokenRegionLength); |
| tokenText.setModuleElement(text.getModuleElement()); |
| tokenText.setOutputFile(text.getOutputFile()); |
| tokenText.setSourceElement(text.getSourceElement()); |
| |
| length += tokenRegionLength; |
| } else if (text.getStartOffset() < tokenEnd && text.getEndOffset() >= tokenEnd |
| && text.getStartOffset() > tokenStart) { |
| // generated region starts with token |
| int tokenRegionLength = tokenEnd - text.getStartOffset(); |
| tokenText = TraceabilityFactory.eINSTANCE.createGeneratedText(); |
| tokenText.setStartOffset(length); |
| tokenText.setEndOffset(length + tokenRegionLength); |
| tokenText.setModuleElement(text.getModuleElement()); |
| tokenText.setOutputFile(text.getOutputFile()); |
| tokenText.setSourceElement(text.getSourceElement()); |
| |
| length += tokenRegionLength; |
| } |
| |
| if (tokenText != null) { |
| InputElement tokenKey = entry.getKey(); |
| Set<GeneratedText> tokenTraces = trace.getTraces().get(tokenKey); |
| if (tokenTraces == null) { |
| tokenTraces = new CompactLinkedHashSet<GeneratedText>(); |
| trace.getTraces().put(tokenKey, tokenTraces); |
| } |
| tokenTraces.add(tokenText); |
| } |
| } |
| } |
| } |
| |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : traceCopy.entrySet()) { |
| entry.getValue().clear(); |
| } |
| traceCopy.clear(); |
| |
| trace.setOffset(length); |
| } |
| |
| /** |
| * This will handle the traceabiliy information changes for all operations that return a Number : |
| * arithmetic operations, String::size(), Collection::Index()... |
| * |
| * @param result |
| * Result of the operation. We'll only leave a single region with its length equal to the given |
| * integer's number of digits. |
| */ |
| private void changeTraceabilityIndicesNumberReturn(Number result) { |
| // We'll keep only the very last trace and alter its indices |
| AbstractTrace trace = visitor.getLastExpressionTrace(); |
| Map.Entry<InputElement, Set<GeneratedText>> lastEntry = null; |
| GeneratedText lastRegion = null; |
| final List<GeneratedText> rejectFromEntry = new ArrayList<GeneratedText>(); |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| Iterator<GeneratedText> textIterator = entry.getValue().iterator(); |
| while (textIterator.hasNext()) { |
| GeneratedText text = textIterator.next(); |
| if (lastRegion == null) { |
| lastRegion = text; |
| lastEntry = entry; |
| } else if (text.getEndOffset() > lastRegion.getEndOffset()) { |
| // lastEntry cannot be null once we get here |
| assert lastEntry != null; |
| if (lastEntry != entry) { |
| lastEntry.getValue().remove(lastRegion); |
| lastEntry = entry; |
| } else { |
| rejectFromEntry.add(lastRegion); |
| } |
| lastRegion = text; |
| } else { |
| textIterator.remove(); |
| } |
| } |
| if (!rejectFromEntry.isEmpty()) { |
| entry.getValue().removeAll(rejectFromEntry); |
| rejectFromEntry.clear(); |
| } |
| } |
| int length = String.valueOf(result).length(); |
| if (lastRegion != null) { |
| lastRegion.setStartOffset(0); |
| lastRegion.setEndOffset(length); |
| } |
| trace.setOffset(length); |
| } |
| |
| /** |
| * This will be in charge of altering traceability indices of the given <em>trace</em> to cope with a |
| * substitute or replace operation. That means changing all traces which offsets overlap with the range |
| * <em>[startIndex, endIndex]</em> so that the given <em>substitutionTrace</em> can be used in this range |
| * ; for a total length of <em>replacementLength</em>. |
| * |
| * @param trace |
| * The trace which offsets are to be altered. |
| * @param startIndex |
| * Starting index of the range where a substring has been replaced. |
| * @param endIndex |
| * Ending index of the range where a substring has been replaced. |
| * @param replacementLength |
| * Total length of the replaced substring. This is not always equal to the length of the range |
| * <em>[startIndex, endIndex]</em> as we <u>can</u> replace a small substring by a large one, |
| * or the opposite ; which also mean that <em>replacementLength</em> <u>can</u> be negative. |
| */ |
| @SuppressWarnings("unchecked") |
| private void changeTraceabilityIndicesOfReplaceOperation(AbstractTrace trace, int startIndex, |
| int endIndex, int replacementLength) { |
| // FIXME Can't we find any shortcut for this? |
| /* |
| * Substrings that will be split in two by replaced substrings will need their own new GeneratedText |
| * instance, this Set will keep track of those so that we can add them in the trace later. |
| */ |
| List<GeneratedText> addedRegions = new ArrayList<GeneratedText>(); |
| |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| Iterator<GeneratedText> textIterator = entry.getValue().iterator(); |
| while (textIterator.hasNext()) { |
| GeneratedText text = textIterator.next(); |
| if (text.getEndOffset() < startIndex) { |
| continue; |
| } |
| /* |
| * This can be one of five cases : 1) the text ends with a replaced substring, 2) the text |
| * starts with a replaced substring, 3) the text contains a replaced substring, 4) the text is |
| * contained within a replaced substring or 5) The text starts after the replacement |
| */ |
| if (text.getStartOffset() < startIndex && text.getEndOffset() == endIndex) { |
| text.setEndOffset(startIndex); |
| } else if (text.getStartOffset() == startIndex && text.getEndOffset() > endIndex) { |
| text.setStartOffset(endIndex + replacementLength); |
| text.setEndOffset(text.getEndOffset() + replacementLength); |
| } else if (text.getStartOffset() < startIndex && text.getEndOffset() > endIndex) { |
| // This instance of a GeneratedText is split in two by the substring |
| // 3.4 compatibility : EcoreUtil.copy() wasn't generic. The cast is necessary |
| @SuppressWarnings("cast") |
| GeneratedText endSubstring = (GeneratedText)EcoreUtil.copy(text); |
| endSubstring.setStartOffset(endIndex + replacementLength); |
| endSubstring.setEndOffset(text.getEndOffset() + replacementLength); |
| text.setEndOffset(startIndex); |
| if (text.eContainer() != null) { |
| // We know this is a list |
| ((List<EObject>)text.eContainer().eGet(text.eContainingFeature())).add(endSubstring); |
| } |
| addedRegions.add(endSubstring); |
| } else if (text.getStartOffset() >= startIndex && text.getEndOffset() <= endIndex) { |
| if (text.eContainer() != null) { |
| EcoreUtil.remove(text); |
| } |
| textIterator.remove(); |
| } else { |
| text.setStartOffset(text.getStartOffset() + replacementLength); |
| text.setEndOffset(text.getEndOffset() + replacementLength); |
| } |
| } |
| // Insert all new regions in the trace |
| entry.getValue().addAll(addedRegions); |
| addedRegions.clear(); |
| } |
| } |
| |
| /** |
| * Substring, trim and possibly other traceability impacting operations share the same "basic" behavior of |
| * altering start and end indices without changing "inside". This behavior is externalized here. |
| * |
| * @param startIndex |
| * Index at which the substring starts. |
| * @param endIndex |
| * Index at which the substring ends. |
| */ |
| private void changeTraceabilityIndicesSubstringReturn(int startIndex, int endIndex) { |
| // If we're currently evaluating a "post" call, we should use the invocation traces. |
| if (visitor.isEvaluatingPostCall()) { |
| // We need the starting index of these traces |
| int offsetGap = -1; |
| final Deque<ExpressionTrace<C>> invocationTraces = visitor.getInvocationTraces(); |
| for (ExpressionTrace<C> trace : invocationTraces) { |
| for (Set<GeneratedText> regions : trace.getTraces().values()) { |
| for (GeneratedText region : regions) { |
| if (offsetGap == -1 || region.getStartOffset() < offsetGap) { |
| offsetGap = region.getStartOffset(); |
| } |
| } |
| } |
| } |
| |
| // actual indices where the substring starts and ends |
| int actualStartIndex = startIndex + offsetGap; |
| int actualEndIndex = endIndex + offsetGap; |
| |
| for (ExpressionTrace<C> trace : invocationTraces) { |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| Iterator<GeneratedText> textIterator = entry.getValue().iterator(); |
| while (textIterator.hasNext()) { |
| GeneratedText text = textIterator.next(); |
| final int initialStart = text.getStartOffset(); |
| final int initialEnd = text.getEndOffset(); |
| final int initialLength = initialEnd - initialStart; |
| |
| GeneratedFile output = text.getOutputFile(); |
| int removedLength = 0; |
| if (initialEnd <= actualStartIndex || initialStart >= actualEndIndex) { |
| textIterator.remove(); |
| EcoreUtil.remove(text); |
| removedLength = initialLength; |
| } else { |
| /* |
| * We have four cases : either 1) the region overlaps with the start index, 2) it |
| * overlaps with the end index, 3) it overlaps with both or 4) it overlaps with |
| * none. |
| */ |
| if (initialStart < actualStartIndex && initialEnd > actualEndIndex) { |
| text.setStartOffset(offsetGap); |
| text.setEndOffset(offsetGap + (endIndex - startIndex)); |
| } else if (initialStart < actualStartIndex) { |
| text.setStartOffset(offsetGap); |
| text.setEndOffset(initialEnd - startIndex); |
| } else if (initialEnd > actualEndIndex) { |
| text.setStartOffset(initialStart - startIndex); |
| text.setEndOffset(offsetGap + (endIndex - startIndex)); |
| } else { |
| text.setStartOffset(initialStart - startIndex); |
| text.setEndOffset(initialEnd - startIndex); |
| } |
| removedLength = initialLength - text.getEndOffset() + text.getStartOffset(); |
| } |
| if (output != null && removedLength > 0) { |
| output.setLength(output.getLength() - removedLength); |
| } |
| } |
| } |
| } |
| } else { |
| final AbstractTrace trace; |
| if (visitor.isInitializingVariable()) { |
| trace = visitor.getInitializingVariableTrace(); |
| } else { |
| trace = visitor.getLastExpressionTrace(); |
| } |
| for (Map.Entry<InputElement, Set<GeneratedText>> entry : trace.getTraces().entrySet()) { |
| Iterator<GeneratedText> textIterator = entry.getValue().iterator(); |
| while (textIterator.hasNext()) { |
| GeneratedText text = textIterator.next(); |
| int removedLength = 0; |
| if (text.getEndOffset() <= startIndex || text.getStartOffset() >= endIndex) { |
| textIterator.remove(); |
| removedLength = text.getEndOffset() - text.getStartOffset(); |
| } else { |
| /* |
| * We have four cases : either 1) the region overlaps with the start index, 2) it |
| * overlaps with the end index, 3) it overlaps with both or 4) it overlaps with none. |
| */ |
| int initialLength = text.getEndOffset() - text.getStartOffset(); |
| if (text.getStartOffset() < startIndex && text.getEndOffset() > endIndex) { |
| text.setStartOffset(0); |
| text.setEndOffset(endIndex - startIndex); |
| } else if (text.getStartOffset() < startIndex) { |
| text.setStartOffset(0); |
| text.setEndOffset(text.getEndOffset() - startIndex); |
| } else if (text.getEndOffset() > endIndex) { |
| text.setStartOffset(text.getStartOffset() - startIndex); |
| text.setEndOffset(endIndex - startIndex); |
| } else { |
| text.setStartOffset(text.getStartOffset() - startIndex); |
| text.setEndOffset(text.getEndOffset() - startIndex); |
| } |
| removedLength = initialLength - text.getEndOffset() + text.getStartOffset(); |
| } |
| trace.setOffset(trace.getOffset() - removedLength); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Computes the additional gap induced by protected area markers in the given source string between the |
| * given indices. |
| * |
| * @param source |
| * The source String. |
| * @param endIndex |
| * Index at which we need to end counting. |
| * @return The additional gap induced by markers between the given indices. |
| */ |
| private int computeAdditionalGap(String source, int endIndex) { |
| final Matcher markerMatcher = Pattern |
| .compile(AcceleoEvaluationVisitor.PROTECTED_AREA_MARKER + "\\{((.)(.)?)\\}").matcher(source.substring(0, endIndex)); //$NON-NLS-1$ |
| |
| int additionalGap = 0; |
| boolean hasMatch = markerMatcher.find(); |
| while (hasMatch) { |
| additionalGap -= AcceleoEvaluationVisitor.PROTECTED_AREA_MARKER.length() + 2; |
| hasMatch = markerMatcher.find(); |
| } |
| return additionalGap; |
| } |
| |
| /** |
| * This will be used to insert the given <code>text</code> in the given <code>trace</code> iff the indices |
| * of this text correspond to one of the trace's texts start or end index. |
| * |
| * @param trace |
| * The trace in which we should insert <code>text</code>. |
| * @param text |
| * The text that is to be inserted if it matches the trace. |
| * @return <code>true</code> if we managed to insert this text in the given trac, <code>false</code> |
| * otherwise. |
| */ |
| private boolean insertTextInTrace(ExpressionTrace<C> trace, GeneratedText text) { |
| boolean insert = false; |
| final Iterator<Set<GeneratedText>> setIterator = trace.getTraces().values().iterator(); |
| while (setIterator.hasNext() && !insert) { |
| Set<GeneratedText> candidate = setIterator.next(); |
| final Iterator<GeneratedText> iterator = candidate.iterator(); |
| while (iterator.hasNext() && !insert) { |
| GeneratedText next = iterator.next(); |
| if (next.getStartOffset() == text.getEndOffset() |
| || next.getEndOffset() == text.getStartOffset()) { |
| insert = true; |
| } |
| } |
| if (insert) { |
| candidate.add(text); |
| } |
| } |
| return insert; |
| } |
| } |