/*******************************************************************************
* Copyright (c) 2006-2013, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.core.common.model;
import java.util.List;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.sax.Utils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.osgi.util.NLS;
import org.xml.sax.helpers.AttributesImpl;
/**
* @author Thomas Hallgren
*/
public class RxPattern extends RxPart {
public static final String TAG = "match"; //$NON-NLS-1$
public static final String ATTR_PATTERN = "pattern"; //$NON-NLS-1$
public static final String ATTR_PREFIX = "prefix"; //$NON-NLS-1$
public static final String ATTR_SUFFIX = "suffix"; //$NON-NLS-1$
private static void addEscapedPattern(StringBuilder bld, String pattern, boolean willBeGroup) throws CoreException {
// No capturing groups permitted. All groups must therefore be converted
// into non capturing groups
//
int orDepth = -1;
int startPos = bld.length();
int parenDepth = 0;
boolean inCharGroup = false;
boolean stripOuter = false;
int top = pattern.length();
for (int idx = 0; idx < top;) {
char c = pattern.charAt(idx++);
switch (c) {
case '\\':
// Next is escaped
//
bld.append(c);
if (idx < top)
c = pattern.charAt(idx++);
break;
case '|':
if (orDepth == -1 || orDepth > parenDepth)
orDepth = parenDepth;
break;
case '[':
inCharGroup = true;
break;
case ']':
inCharGroup = false;
break;
case '(':
if (inCharGroup)
break;
++parenDepth;
if (idx == top)
break;
if (pattern.charAt(idx) != '?') {
// If the pattern starts with a group and this group
// contains the whole pattern
// then it should be stripped off
//
if (idx == 1)
stripOuter = true;
bld.append("(?"); //$NON-NLS-1$
c = ':';
} else {
if (idx == 1 && top > 2)
stripOuter = (pattern.charAt(2) == ':');
}
break;
case ')':
if (inCharGroup)
break;
parenDepth--;
if (parenDepth < 0)
break;
if (parenDepth == 0 && idx < top)
stripOuter = false;
break;
}
bld.append(c);
}
if (parenDepth != 0)
throw BuckminsterException.fromMessage(NLS.bind(Messages.Unbalanced_parenthesis_in_pattern_0, pattern));
if (stripOuter) {
if (willBeGroup || orDepth != 1) {
int tpos = startPos;
int fpos = startPos + 3; // We strip '(?:'
int epos = bld.length() - 1; // and ')'
while (fpos < epos)
bld.setCharAt(tpos++, bld.charAt(fpos++));
bld.setLength(tpos);
}
} else if (!willBeGroup && orDepth == 0) {
// A group must be added to limit what's affected by the
// OR expression
//
String subExpr = bld.substring(startPos, bld.length());
bld.setLength(startPos);
bld.append("(?:"); //$NON-NLS-1$
bld.append(subExpr);
bld.append(')');
}
}
private static void addQuotedString(StringBuilder bld, String str) throws CoreException {
int top = str.length();
for (int idx = 0; idx < top; ++idx) {
char c = str.charAt(idx);
switch (c) {
case '\\':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '.':
case '?':
case '+':
case '*':
case '|':
case '^':
case '$':
bld.append('\\');
}
bld.append(c);
}
}
private final String pattern;
private final String prefix;
private final String suffix;
public RxPattern(String name, boolean optional, String pattern, String prefix, String suffix) {
super(name, optional);
this.pattern = pattern;
this.prefix = prefix;
this.suffix = suffix;
}
@Override
public void addPattern(StringBuilder bld, List<RxPart> namedParts) throws CoreException {
if (!isOptional()) {
addInnerPattern(bld, namedParts, false);
return;
}
// Everything must be in a group that is marked as optional
//
bld.append('(');
if (prefix == null && suffix == null) {
String name = getName();
if (name == null)
bld.append("?:"); // Non capturing group //$NON-NLS-1$
else
namedParts.add(this);
addEscapedPattern(bld, pattern, true);
} else {
// Group as a whole must be a non capturing group
//
bld.append("?:"); //$NON-NLS-1$
addInnerPattern(bld, namedParts, true);
}
bld.append(")?"); //$NON-NLS-1$
}
@Override
public String getDefaultTag() {
return TAG;
}
public String getPattern() {
return pattern;
}
public String getPrefix() {
return prefix;
}
public String getSuffix() {
return suffix;
}
@Override
protected void addAttributes(AttributesImpl attrs) {
super.addAttributes(attrs);
Utils.addAttribute(attrs, ATTR_PATTERN, pattern);
if (prefix != null)
Utils.addAttribute(attrs, ATTR_PREFIX, prefix);
if (suffix != null)
Utils.addAttribute(attrs, ATTR_SUFFIX, suffix);
}
private void addInnerPattern(StringBuilder bld, List<RxPart> namedParts, boolean willBeGroup) throws CoreException {
if (prefix != null)
addQuotedString(bld, prefix);
String name = getName();
if (name != null) {
// Pattern must be a capturing group
//
bld.append('(');
addEscapedPattern(bld, pattern, true);
bld.append(')');
namedParts.add(this);
} else
addEscapedPattern(bld, pattern, willBeGroup && prefix == null && suffix == null);
if (suffix != null)
addQuotedString(bld, suffix);
}
}