/*
* Created on February 20, 2003, 4:02 PM
*/
package org.jboss.tools.common.model.filesystems.impl;
import java.io.StringReader;
import java.util.*;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.model.*;
import org.jboss.tools.common.model.engines.impl.EnginesLoader;
import org.jboss.tools.common.model.event.XModelTreeEvent;
import org.jboss.tools.common.model.filesystems.BodySource;
import org.jboss.tools.common.model.filesystems.impl.AbstractXMLFileImpl;
import org.jboss.tools.common.model.loaders.*;
import org.jboss.tools.common.model.markers.ConstraintChecker;
import org.jboss.tools.common.model.impl.*;
import org.jboss.tools.common.model.util.*;
/**
*
* @author valera & glory
*/
public class AbstractExtendedXMLFileImpl extends AbstractXMLFileImpl {
private static final long serialVersionUID = 7942041044569562286L;
ConstraintChecker constraintChecker = new ConstraintChecker(this);
ThreadSafeCopyFactory threadSafeCopyFactory = null;
public AbstractExtendedXMLFileImpl() {}
/**
* Returns ready to be loaded copy of this object if and only if this object is being loaded by another thread.
* Otherwise, returns null.
*
* @return
*/
XModelObject getThreadSafeCopy() {
return threadSafeCopyFactory == null ? null : threadSafeCopyFactory.getThreadSafeCopy();
}
public boolean hasChildren() {
return true;
}
private static String ns = ".NAME.EXTENSION.overlapped.expanded._body_.actualBodyTimeStamp.correctBody.forceLoad."; //$NON-NLS-1$
public String get(String name) {
if(name.equals("_hasErrors_")) { //$NON-NLS-1$
return super.get(ATTR_NAME_IS_INCORRECT);
}
if (getParent() != null && ns.indexOf("." + name + ".") < 0) { //$NON-NLS-1$ //$NON-NLS-2$
if(loadAttributeSeparately(name)) return super.get(name);
loadChildren();
XModelObject copy = getThreadSafeCopy();
if(copy != null) {
return copy.get(name);
}
}
return super.get(name);
}
/**
* Used to avoid file loading when only one attribute is required.
* This must be used with caution. Now only uri attribute in tld
* is processed as most critical.
* @param xmlname
* @return
*/
protected boolean shouldLoadAttributeSeparately(String xmlname) {
return false;
}
/**
* @see shouldLoadAttributeSeparately.
* @param xmlname
* @return
*/
private boolean loadAttributeSeparately(String xmlname) {
BodySource source = getBodySource();
if(source == null) return threadSafeCopyFactory == null;
if(!shouldLoadAttributeSeparately(xmlname)) return false;
if(xmlname == null || xmlname.length() == 0) return false;
String oldvalue = super.get(xmlname);
if(oldvalue != null && oldvalue.length() > 0) return true;
String body = source.get();
Document doc = XMLUtil.getDocument(new StringReader(body));
if(doc == null) return false;
Element element = doc.getDocumentElement();
if(element == null) return false;
XModelObjectLoaderUtil util = new XModelObjectLoaderUtil();
util.setSaveEntity(false);
String value = util.getAttribute(element, xmlname);
if(value == null || value.length() == 0) return false;
super.set(xmlname, value);
return true;
}
public boolean isObjectEditable() {
return super.isObjectEditable() && (!XModelObjectConstants.YES.equals(get("_hasErrors_"))); //$NON-NLS-1$
}
public XModelObject[] getChildren() {
XModelObject copy = getThreadSafeCopy();
return (copy != null) ? copy.getChildren() : super.getChildren();
}
protected void loadChildren() {
getThreadSafeCopy();
BodySource source = getBodySource();
if (source == null) return;
threadSafeCopyFactory = new ThreadSafeCopyFactory(this);
super.setBodySource(null);
XObjectLoader loader = XModelObjectLoaderUtil.getObjectLoader(this);
String body = source.get();
XModelObjectLoaderUtil.setTempBody(this, body);
loader.load(this);
if(threadSafeCopyFactory != null) {
threadSafeCopyFactory.destroy();
threadSafeCopyFactory = null;
}
if(!isIncorrect() && !isOverlapped()) {
runCheckerOnLoad();
}
}
static class CheckerJob extends WorkspaceJob {
List<AbstractExtendedXMLFileImpl> files = new ArrayList<AbstractExtendedXMLFileImpl>();
public CheckerJob() {
super("Checking constraints on load..."); //$NON-NLS-1$
}
public void add(AbstractExtendedXMLFileImpl file) {
synchronized (files) {
files.add(file);
}
schedule();
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor)
throws CoreException {
while(true) {
AbstractExtendedXMLFileImpl file = null;
synchronized (files) {
if(files.isEmpty()) break;
file = files.remove(0);
}
file.getResourceMarkers().clear();
file.constraintChecker.check();
}
return Status.OK_STATUS;
}
}
static CheckerJob checkerOnLoad = new CheckerJob();
void runCheckerOnLoad() {
if(!isActive()) return;
XModelObject s = getParent();
while(s != null && s.getFileType() != XModelObject.SYSTEM) s = s.getParent();
if(s == null || !s.getModelEntity().getName().equals(XModelObjectConstants.ENT_FILE_SYSTEM_FOLDER)) {
return;
}
/*Runnable r = new Runnable() {
public void run() {
getResourceMarkers().clear();
constraintChecker.check();
}
};
Display.getDefault().asyncExec(r);*/
checkerOnLoad.add(this);
}
public void set(String name, String value) {
super.set(name, value);
if(ATTR_NAME_INCORRECT_BODY.equals(name) && value.length() > 0) { //$NON-NLS-1$
set(ATTR_NAME_IS_INCORRECT, YES);
// setErrors(value, hasDTD(), !hasDTD()); //never validate dtd
int resolution = EntityXMLRegistration.getInstance().resolve(getModelEntity());
if(EntityXMLRegistration.isSystemId(value)) resolution = EntityXMLRegistration.UNRESOLVED;
setErrors(value, resolution == EntityXMLRegistration.DTD, resolution == EntityXMLRegistration.SCHEMA);
}
}
public String getAsText() {
return get(XModelObjectConstants.ATTR_NAME_BODY);
}
public void edit(String body) throws XModelException {
edit(body, false);
}
protected boolean isForceLoadOn() {
return XModelObjectConstants.TRUE.equals(get("forceLoad")); //$NON-NLS-1$
}
public void edit(String body, boolean update) throws XModelException {
if(body == null) return;
if(!isForceLoadOn() && body.equals(getAsText())) return;
String entity = getModel().getEntityRecognizer().getEntityName(new EntityRecognizerContext(toFileName(this), getAttributeValue(XModelObjectConstants.ATTR_NAME_EXTENSION), body));
if(entity == null || !entity.equals(getModelEntity().getName())) {
String[] errors = (body.length() == 0) ? null : XMLUtil.getXMLErrors(new java.io.StringReader(body), false);
if(errors == null || errors.length == 0) errors = new String[]{"Doctype has been changed. Please save file for the change to take effect in object model. :0:0"}; //$NON-NLS-1$
setErrors(body, errors);
XModelImpl m = (XModelImpl)getModel();
m.fireStructureChanged(this);
if(!isOverlapped()) getResourceMarkers().update();
return;
}
boolean errors1 = (XModelObjectConstants.YES.equals(get("_hasErrors_"))); //$NON-NLS-1$
AbstractExtendedXMLFileImpl f = getUpdatedFile(body, true);
if(f == null) return;
f.getChildren();
XModelObjectImpl p = (XModelObjectImpl)getParent();
XModelImpl m = (XModelImpl)getModel();
boolean isOverlapped = isOverlapped();
if(!isOverlapped) getResourceMarkers().clear();
if(f.isIncorrect()) {
getChildren();
boolean fire = this.loaderError == null && f.loaderError != null;
this.loaderError = f.loaderError;
super.set(ATTR_NAME_INCORRECT_BODY, f.get(ATTR_NAME_INCORRECT_BODY));
super.set(ATTR_NAME_IS_INCORRECT, YES);
if(f.get("errors") != null) super.set("errors", f.get("errors")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(fire) changeTimeStamp();
if(f.get("actualBodyTimeStamp") != null && !"-1".equals(f.get("actualBodyTimeStamp"))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
mergeAll(f, update);
}
m.fireStructureChanged(this);
if(!isOverlapped) getResourceMarkers().update();
} else if(isMergingChanges()) {
String oldBody = get(ATTR_NAME_CORRECT_BODY);
boolean changed = body != null && !body.equals(oldBody);
set(ATTR_NAME_CORRECT_BODY, body);
set("actualBodyTimeStamp", "0"); //$NON-NLS-1$ //$NON-NLS-2$
long ots = getTimeStamp();
mergeAll(f, update);
if(changed && ots == getTimeStamp()) {
changeTimeStamp();
}
set("actualBodyTimeStamp", "" + getTimeStamp()); //$NON-NLS-1$ //$NON-NLS-2$
if(errors1) m.fireStructureChanged(this);
if(!isOverlapped) {
check();
}
} else {
//old edit by replace
p.removeChild_0(this);
p.addChild_0(f);
m.fireStructureChanged(p);
f.setModified(true);
if(!isOverlapped) f.getResourceMarkers().update();
}
}
public void check() {
if(!isOverlapped()) {
constraintChecker.check();
}
}
protected void safeChangeTimeStamp() {
boolean b = ("" + getTimeStamp()).equals(get("actualBodyTimeStamp")); //$NON-NLS-1$ //$NON-NLS-2$
changeTimeStamp();
if(b) set("actualBodyTimeStamp", "" + getTimeStamp()); //$NON-NLS-1$ //$NON-NLS-2$
}
protected void mergeAll(XModelObject f, boolean update) throws XModelException {
merge(f, !update);
}
protected boolean isMergingChanges() {
return true;
}
protected String getProcessEntity() {
return null;
}
protected boolean hasDTD() {
return true;
}
private AbstractExtendedXMLFileImpl getUpdatedFile(String body, boolean fire) {
boolean errors1 = (XModelObjectConstants.YES.equals(get("_hasErrors_"))); //$NON-NLS-1$
loaderError = null;
// setErrors(body, hasDTD(), !hasDTD()); //never validate dtd
int resolution = EntityXMLRegistration.getInstance().resolve(getModelEntity());
if(EntityXMLRegistration.isSystemId(body)) resolution = EntityXMLRegistration.UNRESOLVED;
setErrors(body, resolution == EntityXMLRegistration.DTD, resolution == EntityXMLRegistration.SCHEMA);
boolean errors2 = (get("errors") != null && get("errors").length() > 0); //$NON-NLS-1$ //$NON-NLS-2$
if(errors1 && errors2) {
super.set(ATTR_NAME_INCORRECT_BODY, body); //$NON-NLS-1$
if(fire) {
changeTimeStamp();
if(isActive()) ((XModelImpl)getModel()).fireNodeChanged(this, getPath());
setModified(true);
}
}
AbstractExtendedXMLFileImpl f = (AbstractExtendedXMLFileImpl)getModel().createModelObject(getModelEntity().getName(), null);
f.setAttributeValue(XModelObjectConstants.ATTR_NAME, getAttributeValue(XModelObjectConstants.ATTR_NAME));
f.setAttributeValue(XModelObjectConstants.ATTR_NAME_EXTENSION, getAttributeValue(XModelObjectConstants.ATTR_NAME_EXTENSION));
f.setBodySource(new SFBodySource(body));
if(errors2) {
f.set(ATTR_NAME_INCORRECT_BODY, body); //$NON-NLS-1$
f.set("errors", super.get("errors")); //$NON-NLS-1$ //$NON-NLS-2$
}
return f;
}
protected final void merge(XModelObject update, boolean fire) throws XModelException {
if(fire) {
fireObjectChanged(XModelTreeEvent.BEFORE_MERGE);
}
if(!YES.equals(update.get(ATTR_NAME_IS_INCORRECT))) {
super.set(ATTR_NAME_INCORRECT_BODY, ""); //$NON-NLS-1$ //$NON-NLS-2$
super.set(XModelObjectConstants.ATTR_NAME_IS_INCORRECT,XModelObjectConstants.NO);
super.set("errors", ""); //$NON-NLS-1$ //$NON-NLS-2$
loaderError = null;
}
if(update instanceof AbstractExtendedXMLFileImpl) {
loaderError = ((AbstractExtendedXMLFileImpl)update).loaderError;
}
Map<String,XModelObject> map = getChildrenForSaveAsMap();
Set<String> set = null;
XModelObject[] cs = update.getChildren();
String entity = getProcessEntity();
for (int i = 0; i < cs.length; i++) {
if(entity != null && entity.equals(cs[i].getModelEntity().getName())) continue;
XModelObject c = getChildByPath(cs[i].getPathPart());
if(c == null) {
if(set == null) set = EnginesLoader.getChildrenToRemove(map, update);
c = EnginesLoader.findAppropriateChild(set, cs[i], map);
if(c == null) {
c = cs[i].copy();
addChild(c);
} else {
boolean has_id = c.getModelEntity().getAttribute(XModelObjectLoaderUtil.ATTR_ID_NAME) != null;
if(has_id) {
c.removeFromParent();
EnginesLoader.merge(c, cs[i], false);
addChild(c);
} else {
EnginesLoader.merge(c, cs[i], fire);
}
}
} else if(c.getModelEntity().getName().equals(cs[i].getModelEntity().getName())) {
EnginesLoader.merge(c, cs[i], fire);
} else {
removeChild(c);
addChild(cs[i].copy());
}
map.remove(c.getPathPart());
}
Iterator<XModelObject> it = map.values().iterator();
while(it.hasNext()) {
((XModelObject)it.next()).removeFromParent();
}
boolean doFire = false;
for (int i = 0; i < cs.length; i++) {
XModelObject c = getChildByPath(cs[i].getPathPart());
if(c == null) continue;
int ci = getIndexOfChild(c);
if(ci == i) continue;
doFire = true;
move(ci, i, false);
}
if(fire && doFire) ((XModelImpl)getModel()).fireStructureChanged(this);
mergeAttributes(update, fire);
if(fire) {
fireObjectChanged(XModelTreeEvent.AFTER_MERGE);
}
}
static String NO_MERGE_ATTRIBUTES = ".name.extension._lateload.isIncorrect.incorrectBody.expand."; //$NON-NLS-1$
void mergeAttributes(XModelObject update, boolean fire) throws XModelException {
XAttribute[] as = update.getModelEntity().getAttributes();
for (int i = 0; i < as.length; i++) {
String n = as[i].getName();
if(NO_MERGE_ATTRIBUTES.indexOf("." + n + ".") >= 0) continue; //$NON-NLS-1$ //$NON-NLS-2$
XModelObjectLoaderUtil.mergeAttributeComment(this, update, as[i], fire);
String ov = getAttributeValue(n);
String nv = update.getAttributeValue(n);
if(ov == null || ov.equals(nv)) continue;
if(fire) {
getModel().changeObjectAttribute(this, n, nv);
} else {
setAttributeValue(n, nv);
}
}
XModelObjectLoaderUtil.mergeFinalComment(this, update, fire);
}
private Map<String,XModelObject> getChildrenForSaveAsMap() {
String entity = getProcessEntity();
XModelObject[] cs = getChildrenForSave();
Map<String,XModelObject> map = new HashMap<String,XModelObject>();
for (int i = 0; i < cs.length; i++) {
if(entity != null && entity.equals(cs[i].getModelEntity().getName())) continue;
map.put(cs[i].getPathPart(), cs[i]);
}
return map;
}
}
class SFBodySource implements BodySource {
String body;
public SFBodySource(String body) {
this.body = body;
}
public String get() {
return body;
}
public boolean write(Object object) {
return true;
}
}