/*******************************************************************************
* Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Exadel, Inc. and Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.model.util;
import java.util.StringTokenizer;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.loaders.XObjectLoader;
import org.jboss.tools.common.model.loaders.impl.PropertiesLoader;
import org.jboss.tools.common.model.loaders.impl.SimpleWebFileLoader;
/**
* Searches for position of start tag corresponding to model object.
* If, optionally, attribute name is provided, searches for position
* of its value.
* This feature is necessary because model object does not keep its
* position in XML text, being loaded using standard XML parser
* that does not provide location in text.
* The algorithm used here is not 100% safe, and should not be
* used for context changes, rather it can be used for
* synchronizing text selection to tree or diagram selection.
*
*
* @author glory
*/
public class PositionSearcher {
String text;
XModelObject object;
String attribute;
int startPosition;
int endPosition;
boolean selectAttributeName = false;
XModelObjectLoaderUtil util;
PropertiesLoader propertiesLoader = null;
public PositionSearcher() {}
public void init(String text, XModelObject object, String attribute) {
if(attribute != null && attribute.startsWith("&")) { //$NON-NLS-1$
selectAttributeName = true;
attribute = attribute.substring(1);
}
this.text = text;
this.object = object;
this.attribute = attribute;
XModelObject f = object;
while(f != null && f.getFileType() != XModelObject.FILE) f = f.getParent();
if(f != null) {
XObjectLoader loader = XModelObjectLoaderUtil.getObjectLoader(f);
if(loader instanceof SimpleWebFileLoader) {
//TODO have more common case, this does not include WebProcessLoader
SimpleWebFileLoader fileLoader = (SimpleWebFileLoader)loader;
fileLoader.createRootElement(f); // initializes namespaces if available.
util = fileLoader.getUtil();
} else if(loader instanceof PropertiesLoader) {
propertiesLoader = (PropertiesLoader)loader;
}
}
}
public void execute() {
startPosition = -1;
endPosition = -1;
if(text == null || object == null) return;
if(propertiesLoader != null) {
String name = object.getAttributeValue("name"); //$NON-NLS-1$
String dname = object.getAttributeValue("dirtyname"); //$NON-NLS-1$
String nvs = object.getAttributeValue("name-value-separator"); //$NON-NLS-1$
int i = text.indexOf(dname + nvs);
if(i >= 0) {
i = text.indexOf(name, i);
startPosition = i;
endPosition = i + name.length();
}
} else {
TagIterator it = new TagIterator();
it.selectObject(object);
if(it.startPos < 0 || it.endPos < it.startPos) return;
startPosition = it.startPos;
endPosition = it.endPos;
/// findTagEnd();
selectAttribute();
}
}
private void selectAttribute() {
if(attribute == null || attribute.length() == 0) return;
XAttribute a = object.getModelEntity().getAttribute(attribute);
if(isESB(a)) {
findESBAttrPosition(a);
}
String xml = (a == null) ? null : a.getXMLName();
if(xml == null || xml.length() == 0) return;
if(xml.indexOf(".") < 0) { //$NON-NLS-1$
String s = text.substring(startPosition, endPosition);
int i1 = findAttrPosition(s, xml);
if(selectAttributeName) {
if(i1 < 0) return;
startPosition = startPosition + i1;
endPosition = startPosition + xml.length();
return;
}
int i2 = (i1 < 0) ? -1 : s.indexOf('"', i1 + 1);
int i3 = (i2 < 0) ? -1 : s.indexOf('"', i2 + 1);
if(i3 > 0) {
endPosition = startPosition + i3;
startPosition = startPosition + i2 + 1;
}
} else {
xml = xml.substring(0, xml.lastIndexOf('.')); //remove .#text
if(xml.indexOf('.') >= 0) {
xml = xml.substring(xml.lastIndexOf('.') + 1); // simplify - look for the last name only
}
int e1 = nextStartPosition(text, "</" + getTagXMLName(object) + ">", startPosition); //$NON-NLS-1$ //$NON-NLS-2$
String s = e1 < 0 ? "" : text.substring(startPosition, e1); //$NON-NLS-1$
if(xml.length() == 0) {
int i1 = s.indexOf(">"); //$NON-NLS-1$
endPosition = startPosition + i1 + 1;
startPosition = startPosition + e1;
} else if(s.length() > 0) {
int i1a = nextStartPosition(s, "<" + xml + ">", 0); //$NON-NLS-1$ //$NON-NLS-2$
int i1b = nextStartPosition(s, "<" + xml + "/>", 0); //$NON-NLS-1$ //$NON-NLS-2$
int i2 = (i1a < 0) ? -1 : s.indexOf("</", i1a); //$NON-NLS-1$
if(i1a >= 0 && i2 >= 0) {
endPosition = startPosition + i2;
startPosition = startPosition + i1a + 2 + xml.length();
} else if(i1b >= 0) {
endPosition = startPosition + i1b + 3 + xml.length();
startPosition = startPosition + i1b;
}
}
}
}
private String getTagXMLName(XModelObject object) {
String TAG_ATTR = "tag";//$NON-NLS-1$
String result = null;
if(object.getModelEntity().getAttribute(TAG_ATTR) != null) {
result = object.getAttributeValue(TAG_ATTR);
} else {
result = object.getModelEntity().getXMLSubPath();
if(result != null && util != null) {
result = util.applyNamespaceToTag(result);
}
}
return result;
}
private int findAttrPosition(String s, String name) {
int i = s.indexOf(name);
if(i < 0) {
return -1;
}
StringTokenizer st = new StringTokenizer(s, "\"", true); //$NON-NLS-1$
int pos = 0;
boolean inValue = false;
while(st.hasMoreTokens()) {
String t = st.nextToken();
if(t.equals("\"")) { //$NON-NLS-1$
inValue = !inValue;
} else {
if(!inValue) {
int k = t.indexOf(name);
if(k >= 0) {
boolean ok = true;
if(k > 0) {
char c = t.charAt(k - 1);
if(Character.isJavaIdentifierPart(c) || c == '-') ok = false;
}
if(ok) return pos + k;
}
}
}
pos += t.length();
}
return -1;
}
public int getStartPosition() {
return startPosition;
}
public int getEndPosition() {
return endPosition;
}
void findTagEnd() {
if(startPosition < 0) return;
if(attribute != null && attribute.length() > 0) return;
String tagname = getTagXMLName(object);
if(tagname == null || tagname.length() == 0) return;
String start = "<" + tagname; //$NON-NLS-1$
if(text.indexOf(start, startPosition) != startPosition) return;
String finish = "</" + tagname + ">"; //$NON-NLS-1$ //$NON-NLS-2$
int i = text.indexOf(finish, startPosition);
if(i >= 0) endPosition = i + finish.length();
}
class TagIterator {
int startPos;
int endPos;
TagIterator() {
startPos = -1;
endPos = 0;
}
public void selectObject(XModelObject object) {
if(object == null) return;
String xml = getTagXMLName(object);
String token = "<" + xml; //$NON-NLS-1$
if(object.getFileType() == XModelObject.FILE) {
startPos = nextStartPos(token, startPos + 1);
if(startPos >= 0) {
endPos = text.indexOf(">", startPos + 1); //$NON-NLS-1$
if(endPos < 0) endPos = text.indexOf("<", startPos + 1); //$NON-NLS-1$
if(endPos < 0) endPos = text.length(); else endPos++;
}
} else if(token.equals("<")) { //$NON-NLS-1$
selectObject(object.getParent());
} else {
XModelObject parent = object.getParent();
selectObject(parent);
if(startPos < 0) return;
// String entity = object.getModelEntity().getName();
XModelObject[] os = parent.getChildren();
for (int i = 0; i < os.length; i++) {
String xml_i = getTagXMLName(os[i]);
if(!xml.equals(xml_i)) continue;
boolean ok = false;
while(!ok) {
startPos = nextStartPos(token, startPos + 1);
if(startPos < 0) return;
if(startPos + token.length() == text.length()) {
ok = true;
} else {
char ch = text.charAt(startPos + token.length());
ok = Character.isWhitespace(ch) || ch == '/' || ch == '>';
}
}
if(os[i] == object) {
endPos = text.indexOf(">", startPos + 1); //$NON-NLS-1$
if(endPos < 0) endPos = text.indexOf("<", startPos + 1); //$NON-NLS-1$
if(endPos < 0) endPos = text.length(); else endPos++;
return;
}
}
}
}
int nextStartPos(String token, int b) {
return nextStartPosition(text, token, b);
}
}
static int nextStartPosition(String text, String token, int b) {
int s = text.indexOf(token, b);
if(s < 0) return s;
int cb = text.indexOf("<!--", b); //$NON-NLS-1$
if(cb < 0 || cb > s) return s;
int ce = text.indexOf("-->", cb); //$NON-NLS-1$
if(ce < 0) return -1;
return nextStartPosition(text, token, ce + 3);
}
/**
* Temporal solution; should be refactored to a framework.
*
*/
boolean isESB(XAttribute a) {
return a != null && "true".equals(a.getProperty("pre"));
}
void findESBAttrPosition(XAttribute a) {
int ep = text.indexOf("</action>", startPosition);
if(ep < 0) return;
String dt = text.substring(startPosition, ep);
String name = "name=\"" + a.getXMLName() + "\"";
int name_i = dt.indexOf(name);
if(name_i < 0) return;
int ps = dt.lastIndexOf("<property", name_i);
if(ps < 0) return;
int pe = dt.indexOf("/>", ps);
if(pe < 0) return;
String dt2 = dt.substring(ps, pe + 2);
int value_i = dt2.indexOf("value=");
if(value_i >= 0) {
int i2 = dt2.indexOf('"', value_i);
int i3 = (i2 < 0) ? -1 : dt2.indexOf('"', i2 + 1);
if(i3 > i2 + 1) {
startPosition = startPosition + ps + i2 + 1;
endPosition = startPosition + i3 - i2 - 1;
return;
} else if(i3 == i2 + 1) {
startPosition = startPosition + ps + i2;
endPosition = startPosition + 2;
return;
}
}
startPosition = startPosition + ps;
endPosition = startPosition + pe + 2 - ps;
}
}