/* * 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; } }