/*******************************************************************************
* Copyright (c) 2011 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.mylyn.docs.intent.parser.modelingunit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.mylyn.docs.intent.core.document.IntentDocumentPackage;
import org.eclipse.mylyn.docs.intent.core.document.TypeLabel;
import org.eclipse.mylyn.docs.intent.core.document.UnitInstruction;
import org.eclipse.mylyn.docs.intent.core.modelingunit.AbstractValue;
import org.eclipse.mylyn.docs.intent.core.modelingunit.AffectationOperator;
import org.eclipse.mylyn.docs.intent.core.modelingunit.AnnotationDeclaration;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ContributionInstruction;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ExternalContentReference;
import org.eclipse.mylyn.docs.intent.core.modelingunit.InstanciationInstruction;
import org.eclipse.mylyn.docs.intent.core.modelingunit.InstanciationInstructionReference;
import org.eclipse.mylyn.docs.intent.core.modelingunit.IntentReferenceInModelingUnit;
import org.eclipse.mylyn.docs.intent.core.modelingunit.LabelInModelingUnit;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnit;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnitFactory;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnitInstruction;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ModelingUnitInstructionReference;
import org.eclipse.mylyn.docs.intent.core.modelingunit.NativeValue;
import org.eclipse.mylyn.docs.intent.core.modelingunit.NewObjectValue;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ReferenceValue;
import org.eclipse.mylyn.docs.intent.core.modelingunit.ResourceDeclaration;
import org.eclipse.mylyn.docs.intent.core.modelingunit.StructuralFeatureAffectation;
import org.eclipse.mylyn.docs.intent.core.modelingunit.TypeReference;
import org.eclipse.mylyn.docs.intent.parser.modelingunit.parser.linker.ModelingUnitLinker;
import org.eclipse.mylyn.docs.intent.parser.modelingunit.parser.utils.Location;
import org.eclipse.mylyn.docs.intent.parser.modelingunit.parser.utils.ModelingUnitContentManager;
/**
* Parser of a Modeling Unit implementation.
*
* @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
*/
public class ModelingUnitParserImpl implements ModelingUnitParser {
/**
* Constant for quotes.
*/
private static final String QUOTE = "\"";
/**
* Common expression for literals.
*/
private static final String STRING_REGEX = "([a-zA-z0-9.:_-]+)"; //$NON-NLS-1$
/**
* Common expression for quoted strings.
*/
private static final String STRING_WITH_QUOTES_REGEX = "(\"[^\"]*\")"; //$NON-NLS-1$
/**
* Common token delimiter.
*/
private static final String TOKEN_DELIMITER = ","; //$NON-NLS-1$
/**
* StandaloneParsingTests constructor.
*/
public ModelingUnitParserImpl() {
init();
}
/**
* Returns true if the given String can be parsed by this parser.
*
* @param contentToParse
* the content to inspect
* @return true if the given string can be parsed by this parser (i.e is a modeling Unit : '@M ... M@')
*/
public boolean isParserFor(String contentToParse) {
return contentToParse.startsWith(MODELING_UNIT_PREFIX)
&& contentToParse.endsWith(MODELING_UNIT_SUFFIX);
}
/**
* Launch the parser in standalone mode and register EPackages.
*/
private void init() {
registerEPackages();
}
/**
* Register the EPackages needed by the parser.
*/
private void registerEPackages() {
IntentDocumentPackage.eINSTANCE.eClass();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.mylyn.docs.intent.parser.modelingunit.ModelingUnitParser#parseString(java.lang.String)
*/
public EObject parseString(String contentToParse) throws ParseException {
return parseString(0, contentToParse);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.mylyn.docs.intent.parser.modelingunit.ModelingUnitParser#parseString(int,
* java.lang.String)
*/
public EObject parseString(int rootOffset, String stringToParse) throws ParseException {
// Root creation
ModelingUnit modelingUnit = ModelingUnitFactory.eINSTANCE.createModelingUnit();
Pattern modelingUnitPattern = Pattern.compile("@M([ \t\f]+" + STRING_REGEX + ")?([ \t\f]+\\[(" //$NON-NLS-1$ //$NON-NLS-2$
+ STRING_REGEX + ")\\])?\\s*"); //$NON-NLS-1$
Matcher matcher = modelingUnitPattern.matcher(stringToParse);
matcher.find();
if (matcher.group(2) != null) {
modelingUnit.setName(matcher.group(2));
}
int startOffset = matcher.group().length();
int endOffset = stringToParse.lastIndexOf("M@");
// Content detection
ModelingUnitContentManager<UnitInstruction> manager = new ModelingUnitContentManager<UnitInstruction>();
manager.addAllContent(getResourceDeclarations(rootOffset, stringToParse));
manager.addAllContent(getInstanciationInstructions(rootOffset, stringToParse, true));
manager.addAllContent(getContributionInstructions(rootOffset, stringToParse, true));
manager.addAllContent(getIntentSectionReferencesinModelingUnit(stringToParse));
manager.addAllContent(getAnnotationDeclarations(stringToParse));
manager.addAllContent(getLabelsinModelingUnit(stringToParse));
manager.addAllContent(getExternalContentReferencesInModelingUnit(stringToParse));
manager.validateContent(stringToParse, startOffset, endOffset, rootOffset);
modelingUnit.getInstructions().addAll(manager.getContent().values());
// Link resolver call
new ModelingUnitLinker().resolveInternalLinks(modelingUnit);
return modelingUnit;
}
/**
* Detects and instantiates {@link ContributionInstruction} occurrences in the given string.
*
* @param rootOffset
* the root offset, used to compute errors locations
* @param string
* the string to analyze
* @param lineBreak
* indicates whether the instruction happens after a line break or not
* @return the map of occurrences found by start offset
* @throws ParseException
* if there is an unclosed block
*/
@SuppressWarnings("unchecked")
private Map<Location, UnitInstruction> getContributionInstructions(int rootOffset, String string,
boolean lineBreak) throws ParseException {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern startPattern = Pattern.compile("^\\s*" + STRING_REGEX + "\\s*\\{\\s*", Pattern.MULTILINE //$NON-NLS-1$ //$NON-NLS-2$
| Pattern.DOTALL);
Matcher matcher = startPattern.matcher(string);
int index = 0;
while (matcher.find(index)) {
ContributionInstruction instance = ModelingUnitFactory.eINSTANCE.createContributionInstruction();
instance.setLineBreak(lineBreak);
ModelingUnitInstructionReference ref = ModelingUnitFactory.eINSTANCE
.createModelingUnitInstructionReference();
ref.setIntentHref(matcher.group(1));
instance.setContributionReference(ref);
// Content detection
index = matcher.group().length() + matcher.start();
try {
int endIndex = getEndIndex(string, index, '}');
String stringContent = string.substring(index, endIndex);
ModelingUnitContentManager<UnitInstruction> manager = new ModelingUnitContentManager<UnitInstruction>();
manager.addAllContent(getStructuralFeatureAffectations(rootOffset + index, stringContent));
manager.addAllContent(getIntentSectionReferencesinModelingUnit(stringContent));
manager.addAllContent(getAnnotationDeclarations(stringContent));
manager.addAllContent(getLabelsinModelingUnit(stringContent));
manager.validateContent(stringContent, index + rootOffset);
instance.getContributions().addAll(
(Collection<? extends ModelingUnitInstruction>)manager.getContent().values());
index = endIndex;
} catch (IndexOutOfBoundsException e) {
int spaceLength = 0;
Matcher spaceMatcher = Pattern.compile("^\\s+").matcher(matcher.group());
if (spaceMatcher.find()) {
spaceLength += spaceMatcher.group().length();
}
throw new ParseException(
Messages.getString(
"ModelingUnitParserImpl.INCORRECT_CONTRIBUTION_END", matcher.group().trim()), spaceLength + rootOffset + matcher.start(), matcher.group().trim().length()); //$NON-NLS-1$
}
res.put(new Location(matcher.start(), index), instance);
}
return res;
}
/**
* Detects and instantiates {@link InstanciationInstruction} occurrences in the given string.
*
* @param rootOffset
* the root offset, used to compute errors locations
* @param string
* the string to analyze
* @param lineBreak
* indicates whether the instruction happens after a line break or not
* @return the map of occurrences found by start offset
* @throws ParseException
* if there is an unclosed block
*/
@SuppressWarnings("unchecked")
private Map<Location, UnitInstruction> getInstanciationInstructions(int rootOffset, String string,
boolean lineBreak) throws ParseException {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern startPattern = Pattern.compile("new\\s+" + STRING_REGEX + "(\\s+" + STRING_REGEX //$NON-NLS-1$ //$NON-NLS-2$
+ ")?\\s*\\{\\s*", Pattern.MULTILINE | Pattern.DOTALL); //$NON-NLS-1$
Matcher matcher = startPattern.matcher(string);
int index = 0;
while (matcher.find(index)) {
InstanciationInstruction instance = ModelingUnitFactory.eINSTANCE
.createInstanciationInstruction();
instance.setLineBreak(lineBreak);
TypeReference typeReference = ModelingUnitFactory.eINSTANCE.createTypeReference();
if (matcher.group(3) != null) {
instance.setName(matcher.group(3));
}
typeReference.setTypeName(matcher.group(1));
instance.setMetaType(typeReference);
// Content detection
index = matcher.group().length() + matcher.start();
try {
int endIndex = getEndIndex(string, index, '}');
String stringContent = string.substring(index, endIndex);
ModelingUnitContentManager<UnitInstruction> manager = new ModelingUnitContentManager<UnitInstruction>();
manager.addAllContent(getStructuralFeatureAffectations(rootOffset + index, stringContent));
manager.validateContent(stringContent, rootOffset + index);
instance.getStructuralFeatures().addAll(
(Collection<? extends StructuralFeatureAffectation>)manager.getContent().values());
index = endIndex;
} catch (IndexOutOfBoundsException e) {
throw new ParseException(
Messages.getString(
"ModelingUnitParserImpl.INCORRECT_INSTANCIATION_END", matcher.group().trim()), rootOffset + matcher.start(), matcher.group().trim().length()); //$NON-NLS-1$
}
res.put(new Location(matcher.start(), index), instance);
}
return res;
}
/**
* Detects and instantiates {@link ResourceDeclaration} occurrences in the given string.
*
* @param rootOffset
* the root offset, used to compute errors locations
* @param string
* the string to analyze
* @return the map of occurrences found by start offset
* @throws ParseException
* if there is an unclosed block
*/
private Map<Location, UnitInstruction> getResourceDeclarations(int rootOffset, String string)
throws ParseException {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern startPattern = Pattern.compile("Resource\\s+" + STRING_REGEX + "\\s*\\{", //$NON-NLS-1$ //$NON-NLS-2$
Pattern.MULTILINE | Pattern.DOTALL);
Matcher matcher = startPattern.matcher(string);
int index = 0;
while (matcher.find(index)) {
ResourceDeclaration instance = ModelingUnitFactory.eINSTANCE.createResourceDeclaration();
instance.setLineBreak(true); // fixed by default
instance.setName(matcher.group(1));
// Content detection
index = matcher.group().length() + matcher.start();
try {
int endIndex = getEndIndex(string, index, '}');
String stringContent = string.substring(index, endIndex);
ModelingUnitContentManager<Object> manager = new ModelingUnitContentManager<Object>();
for (Affectation affectation : getAllAffectations(rootOffset + index, stringContent)) {
if ("URI".equals(affectation.key)) { //$NON-NLS-1$
instance.setUri(URI.createURI(affectation.values.get(0).replace(QUOTE, "")));
manager.addContent(affectation.location, "URI");
} else if ("contentType".equals(affectation.key)) { //$NON-NLS-1$
instance.setContentType(affectation.values.get(0));
manager.addContent(affectation.location, "contentType");
} else if ("content".equals(affectation.key)) { //$NON-NLS-1$
for (String value : affectation.values) {
ModelingUnitInstructionReference ref = ModelingUnitFactory.eINSTANCE
.createModelingUnitInstructionReference();
ref.setIntentHref(value);
instance.getContent().add(ref);
}
manager.addContent(affectation.location, "content");
}
}
manager.validateContent(stringContent, rootOffset + index);
index = endIndex;
} catch (IndexOutOfBoundsException e) {
throw new ParseException(
Messages.getString(
"ModelingUnitParserImpl.INCORRECT_RESOURCE_DECLARATION_END", matcher.group().trim()), rootOffset + matcher.start(), matcher.group().length()); //$NON-NLS-1$
}
res.put(new Location(matcher.start(), index), instance);
}
return res;
}
/**
* Detects and instantiates {@link IntentReferenceinModelingUnit} occurrences in the given string.
*
* @param string
* the string to analyze
* @return the map of occurrences found by start offset
*/
private Map<Location, UnitInstruction> getIntentSectionReferencesinModelingUnit(String string) {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern pattern = Pattern.compile("@see\\s+" + STRING_WITH_QUOTES_REGEX + "(\\s+(" //$NON-NLS-1$ //$NON-NLS-2$
+ STRING_WITH_QUOTES_REGEX + "))?"); //$NON-NLS-1$
Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
IntentReferenceInModelingUnit ref = ModelingUnitFactory.eINSTANCE
.createIntentReferenceInModelingUnit();
ref.setLineBreak(true); // fixed by default
ref.setIntentHref(matcher.group(1).replaceAll(QUOTE, ""));
if (matcher.group(3) != null) {
ref.setTextToPrint(matcher.group(3).replaceAll(QUOTE, ""));
}
res.put(new Location(matcher.start(), matcher.end()), ref);
}
return res;
}
/**
* Detects and instantiates lazy {@link LabelinModelingUnit} occurrences in the given string.
*
* @param string
* the string to analyze
* @return the map of occurrences found by start offset
*/
private Map<Location, UnitInstruction> getLabelsinModelingUnit(String string) {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern pattern = Pattern.compile("@(lazy)?label\\s+" + STRING_WITH_QUOTES_REGEX + "(\\s+(" //$NON-NLS-1$ //$NON-NLS-2$
+ STRING_WITH_QUOTES_REGEX + "))?"); //$NON-NLS-1$
Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
LabelInModelingUnit instance = ModelingUnitFactory.eINSTANCE.createLabelInModelingUnit();
instance.setLabelValue(matcher.group(2));
instance.setLineBreak(true); // fixed by default
if ("lazy".equals(matcher.group(1))) { //$NON-NLS-1$
instance.setType(TypeLabel.LAZY);
} else {
instance.setType(TypeLabel.EXPLICIT);
}
if (matcher.group(4) != null) {
instance.setTextToPrint(matcher.group(4));
}
res.put(new Location(matcher.start(), matcher.end()), instance);
}
return res;
}
/**
* Detects and instantiates {@link AnnotationDeclaration} occurrences in the given string.
*
* @param string
* the string to analyze
* @return the map of occurrences found by start offset
*/
private Map<Location, UnitInstruction> getAnnotationDeclarations(String string) {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern pattern = Pattern.compile("@Annotation\\s+" + STRING_REGEX + "(.+)"); //$NON-NLS-1$ //$NON-NLS-2$
Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
AnnotationDeclaration instance = ModelingUnitFactory.eINSTANCE.createAnnotationDeclaration();
instance.setLineBreak(true); // fixed by default
instance.setAnnotationID(matcher.group(1));
// Parameters map detection
for (String token : customTokenizer(matcher.group(2), TOKEN_DELIMITER)) {
Matcher entryMatcher = Pattern.compile(STRING_REGEX + "\\s*=\\s*" + STRING_WITH_QUOTES_REGEX, //$NON-NLS-1$
Pattern.MULTILINE | Pattern.DOTALL).matcher(token);
if (entryMatcher.find()) {
instance.getMap().put(entryMatcher.group(1), entryMatcher.group(2));
}
}
res.put(new Location(matcher.start(), matcher.end()), instance);
}
return res;
}
/**
* Detects and instantiates {@link ExternalContentReference} occurrences in the given string.
*
* @param string
* the string to analyze
* @return the map of occurrences found by start offset
*/
private Map<Location, UnitInstruction> getExternalContentReferencesInModelingUnit(String string) {
Map<Location, UnitInstruction> res = new HashMap<Location, UnitInstruction>();
Pattern pattern = Pattern.compile(EXTERNAL_CONTENT_REFERENCE + "\\s+" + STRING_WITH_QUOTES_REGEX); //$NON-NLS-1$ //$NON-NLS-2$
Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
ExternalContentReference instance = ModelingUnitFactory.eINSTANCE
.createExternalContentReference();
instance.setLineBreak(true); // fixed by default
instance.setUri(URI.createURI(matcher.group(1).replace(QUOTE, "")));
res.put(new Location(matcher.start(), matcher.end()), instance);
}
return res;
}
/**
* Detects and instantiates {@link StructuralFeatureAffectation} occurrences in the given string.
*
* @param rootOffset
* the root offset, used to compute errors locations
* @param string
* the string to analyze
* @return the map of occurrences found by start offset
* @throws ParseException
* if there is an unclosed block
*/
private Map<Location, UnitInstruction> getStructuralFeatureAffectations(int rootOffset, String string)
throws ParseException {
ModelingUnitContentManager<UnitInstruction> manager = new ModelingUnitContentManager<UnitInstruction>();
for (Affectation affectation : getAllAffectations(rootOffset, string)) {
StructuralFeatureAffectation instance = ModelingUnitFactory.eINSTANCE
.createStructuralFeatureAffectation();
instance.setLineBreak(true); // fixed by default
instance.setName(affectation.key);
if (affectation.hasMultipleOperator) {
instance.setUsedOperator(AffectationOperator.MULTI_VALUED_AFFECTATION);
}
for (String valueContent : affectation.values) {
AbstractValue value = getValue(rootOffset + affectation.keyLength, valueContent);
instance.getValues().add(value);
}
manager.addContent(affectation.location, instance);
}
return manager.getContent();
}
/**
* Detects and instantiates {@link AbstractValue} occurrences in the given string.
*
* @param rootOffset
* the root offset, used to compute errors locations
* @param initialString
* the string to analyze
* @return the map of occurrences found by start offset
* @throws ParseException
* if there is an unclosed block
*/
private AbstractValue getValue(int rootOffset, String initialString) throws ParseException {
AbstractValue res = null;
String string = initialString.trim();
if (Pattern.compile(STRING_WITH_QUOTES_REGEX).matcher(string).matches()) {
NativeValue nativeValue = ModelingUnitFactory.eINSTANCE.createNativeValue();
nativeValue.setValue(string.trim());
res = nativeValue;
} else if (Pattern.compile(STRING_REGEX).matcher(string).matches()) {
ReferenceValue referenceValue = ModelingUnitFactory.eINSTANCE.createReferenceValue();
InstanciationInstructionReference referencedInstanciation = ModelingUnitFactory.eINSTANCE
.createInstanciationInstructionReference();
referencedInstanciation.setInstanceName(string);
referenceValue.setInstanciationReference(referencedInstanciation);
res = referenceValue;
} else {
Map<Location, UnitInstruction> instructions = getInstanciationInstructions(rootOffset, string,
false);
if (!instructions.isEmpty()) {
NewObjectValue newValue = ModelingUnitFactory.eINSTANCE.createNewObjectValue();
newValue.setValue((InstanciationInstruction)instructions.values().iterator().next());
res = newValue;
}
}
if (res == null) {
throw new ParseException("Unrecognized structural feature value", rootOffset, string.length());
}
return res;
}
/**
* A data structure to store affectations.
*/
private class Affectation {
/**
* Indicates if this affecation has multiple operator.
*/
boolean hasMultipleOperator;
/**
* The Affectation location.
*/
Location location;
/**
* The key length.
*/
int keyLength;
/**
* The key.
*/
String key;
/**
* List of values.
*/
List<String> values = new ArrayList<String>();
}
/**
* Computes the list of affectation in the given String.
*
* @param offset
* the root offset
* @param string
* the string to analyze
* @return the list of affectations
* @throws ParseException
* if there is an unclosed block
*/
private List<Affectation> getAllAffectations(int offset, String string) throws ParseException {
List<Affectation> allAffectations = new ArrayList<Affectation>();
allAffectations.addAll(getSingleAffectations(offset, string));
allAffectations.addAll(getMultipleAffectations(offset, string));
return allAffectations;
}
/**
* Computes the list of single affectation in the given String.
*
* @param offset
* the root offset
* @param string
* the string to analyze
* @return the list of single affectations
* @throws ParseException
* if there is an unclosed block
*/
private List<Affectation> getSingleAffectations(int offset, String string) throws ParseException {
List<Affectation> res = new ArrayList<Affectation>();
Pattern startPattern = Pattern.compile(STRING_REGEX + "\\s*(\\+?)=\\s*"); //$NON-NLS-1$
Matcher matcher = startPattern.matcher(string);
int index = 0;
int middleOffset = 0;
while (matcher.find(index)) {
index = matcher.group().length() + matcher.start();
middleOffset = index;
try {
int endIndex = getEndIndex(string, index, ';');
String valuesContent = string.substring(index, endIndex);
if (!valuesContent.startsWith("[")) {
Affectation affectation = new Affectation();
affectation.hasMultipleOperator = !"".equals(matcher.group(2)); //$NON-NLS-1$
affectation.key = matcher.group(1);
affectation.values.add(valuesContent);
affectation.keyLength = middleOffset;
// index = the last endIndex
affectation.location = new Location(matcher.start(), endIndex);
res.add(affectation);
}
index = endIndex;
} catch (IndexOutOfBoundsException e) {
throw new ParseException(
Messages.getString(
"ModelingUnitParserImpl.INCORRECT_SINGLE_AFFECTATION_END", matcher.group().trim()), offset + matcher.start(), matcher.group().length()); //$NON-NLS-1$
}
}
return res;
}
/**
* Computes the list of multiple affectation in the given String.
*
* @param offset
* the root offset
* @param string
* the string to analyze
* @return the list of multiple affectations
* @throws ParseException
* if there is an unclosed block
*/
private List<Affectation> getMultipleAffectations(int offset, String string) throws ParseException {
List<Affectation> res = new ArrayList<Affectation>();
Pattern startPattern = Pattern.compile(STRING_REGEX + "\\s*\\+=\\s*\\["); //$NON-NLS-1$
Matcher matcher = startPattern.matcher(string);
int index = 0;
int middleOffset = 0;
while (matcher.find(index)) {
Affectation affectation = new Affectation();
affectation.hasMultipleOperator = true;
affectation.key = matcher.group(1);
index = matcher.group().length() + matcher.start();
middleOffset = index;
try {
int endIndex = getEndIndex(string, index, ']');
String valuesContent = string.substring(index, endIndex);
affectation.values.addAll(customTokenizer(valuesContent, TOKEN_DELIMITER));
index = endIndex;
} catch (IndexOutOfBoundsException e) {
throw new ParseException(
Messages.getString(
"ModelingUnitParserImpl.INCORRECT_MULTIPLE_AFFECTATION_END", matcher.group().trim()), offset + matcher.start(), matcher.group().length()); //$NON-NLS-1$
}
affectation.keyLength = middleOffset;
affectation.location = new Location(matcher.start(), getEndIndex(string, index, ';'));
res.add(affectation);
}
return res;
}
/**
* Computes the ending index of the current element.
*
* @param string
* the entire string
* @param start
* the offset where to start the lookup
* @param end
* the looked up char
* @return the ending index
* @throws IndexOutOfBoundsException
* if no ending index has been found
*/
private static int getEndIndex(String string, int start, char end) throws IndexOutOfBoundsException {
char[] charArray = string.toCharArray();
for (int i = start; i < charArray.length; i++) {
char c = charArray[i];
// CHECKSTYLE:OFF : We modify the i control variable in order to "jump".
switch (charArray[i]) {
case '"':
i = string.indexOf('"', i + 1);
if (i < 0) {
throw new IndexOutOfBoundsException();
}
break;
case '{':
i = getEndIndex(string, i + 1, '}');
break;
case '[':
i = getEndIndex(string, i + 1, ']');
break;
default:
if (c == end) {
return i;
}
break;
}
// CHECKSTYLE:ON
}
throw new IndexOutOfBoundsException();
}
/**
* A custom Tokenizer.
* <ul>
* <li>tokenizes aware of quotes</li>
* <li>if no delimiter is found, returns one token anyway</li>
* <li>trims all returned tokens</li>
* </ul>
*
* @param string
* the string to tokenize
* @param delimiter
* the tokens delimiter
* @return the list of found tokens
*/
private static List<String> customTokenizer(String string, String delimiter) {
final String skipQualifier = "@SKIPPED_QUOTE_";
// Detects and replace quoted strings by qualifiers
String textWithSkippedQuotes = string;
int index = 0;
List<String> skippedQuotes = new ArrayList<String>();
Matcher m = Pattern.compile(STRING_WITH_QUOTES_REGEX).matcher(string);
while (m.find()) {
if (m.group(1) != null) {
textWithSkippedQuotes = textWithSkippedQuotes.replaceFirst(m.group(1), skipQualifier //$NON-NLS-1$
+ index++);
skippedQuotes.add(m.group(1));
}
}
// Tokenizes, then replace quoted string qualifiers and trims
List<String> res = new ArrayList<String>();
Pattern quotesPattern = Pattern.compile(skipQualifier + "([0-9])+"); //$NON-NLS-1$
if (textWithSkippedQuotes.contains(delimiter)) {
StringTokenizer tokenizer = new StringTokenizer(textWithSkippedQuotes, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
Matcher quotesMatcher = quotesPattern.matcher(token);
while (quotesMatcher.find()) {
token = token.replaceFirst(quotesMatcher.group(),
skippedQuotes.get(new Integer(quotesMatcher.group(1))));
}
res.add(token.trim());
}
} else {
Matcher quotesMatcher = quotesPattern.matcher(textWithSkippedQuotes);
while (quotesMatcher.find()) {
textWithSkippedQuotes = textWithSkippedQuotes.replaceFirst(quotesMatcher.group(),
skippedQuotes.get(new Integer(quotesMatcher.group(1))));
}
res.add(textWithSkippedQuotes);
}
return res;
}
}