/******************************************************************************* * 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.impl; import java.io.File; import java.io.PrintWriter; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.osgi.util.NLS; import org.jboss.tools.common.meta.XAttribute; import org.jboss.tools.common.meta.XMapping; import org.jboss.tools.common.meta.XModelEntity; import org.jboss.tools.common.meta.XModelMetaData; import org.jboss.tools.common.meta.action.XEntityData; import org.jboss.tools.common.meta.action.impl.XEntityDataImpl; import org.jboss.tools.common.meta.constraint.XAttributeConstraint; import org.jboss.tools.common.meta.constraint.XAttributeConstraintV; import org.jboss.tools.common.model.ServiceDialog; import org.jboss.tools.common.model.XModel; import org.jboss.tools.common.model.XModelBuffer; import org.jboss.tools.common.model.XModelConstants; import org.jboss.tools.common.model.XModelException; import org.jboss.tools.common.model.XModelObjectConstants; import org.jboss.tools.common.model.XModelObject; import org.jboss.tools.common.model.XModelTransferBuffer; import org.jboss.tools.common.model.event.XModelChangeManager; import org.jboss.tools.common.model.event.XModelTreeEvent; import org.jboss.tools.common.model.event.XModelTreeListener; import org.jboss.tools.common.model.filesystems.FileSystemsHelper; import org.jboss.tools.common.model.filesystems.impl.AbstractExtendedXMLFileImpl; import org.jboss.tools.common.model.filesystems.impl.FileSystemImpl; import org.jboss.tools.common.model.filesystems.impl.FileSystemPeer; import org.jboss.tools.common.model.loaders.EntityRecognizer; import org.jboss.tools.common.model.loaders.XObjectLoader; import org.jboss.tools.common.model.loaders.impl.ModelEntityRecognizer; import org.jboss.tools.common.model.plugin.ModelMessages; import org.jboss.tools.common.model.plugin.ModelPlugin; import org.jboss.tools.common.model.undo.XChangeUndo; import org.jboss.tools.common.model.undo.XUndoManager; import org.jboss.tools.common.model.util.ModelFeatureFactory; import org.jboss.tools.common.model.util.XModelObjectLoaderUtil; import org.jboss.tools.common.util.FileUtil; public class XModelImpl implements XModel { private XModelMetaData metadata = null; private Properties properties = null; private ArrayList<XModelTreeListener> treeListeners = new ArrayList<XModelTreeListener>(); private XModelTreeListener[] treeListenersArray = new XModelTreeListener[0]; private XModelObject root = null; private XUndoManager undoer = new XUndoManager(); private static ServiceDialog service = null; private static XModelBufferImpl buffer = new XModelBufferImpl(); private XModelChangeManager changemanager = new XModelChangeManager(); private FileSystemPeer fileregistry = new FileSystemPeer(); private static PrintWriter out = new PrintWriter(System.out, true); private HashMap<String,XModelObject> extraroots = new HashMap<String,XModelObject>(2); private String rootEntity = XModelObjectConstants.ROOT_OBJECT; public XModelImpl(Properties properties, XModelMetaData metadata) { this.metadata = metadata; this.properties = properties; if(properties.getProperty(XModelObjectConstants.PROP_ROOT_ENTITY) != null) { rootEntity = properties.getProperty(XModelObjectConstants.PROP_ROOT_ENTITY); } XModelConstants.validate(this); } public XModelMetaData getMetaData() { return metadata; } public Properties getProperties() { return (Properties)properties; } public PrintWriter getOut() { return out; } public void setOut(PrintWriter out) { if(out != null) this.out = out; } public void setService(ServiceDialog _service) { service = _service; if(service != null) service.setModel(this); } public ServiceDialog getService() { return service; } public XUndoManager getUndoManager() { return undoer; } public XModelBuffer getModelBuffer() { if(XModelTransferBuffer.getInstance().isEnabled()) { return XModelTransferBuffer.getInstance().getBuffer(); } return buffer; } public EntityRecognizer getEntityRecognizer() { return metadata.getEntityRecognizer(); } public XModelChangeManager getChangeManager() { return changemanager; } public FileSystemPeer getFileRegistry() { return fileregistry; } public XModelObject getRoot() { if(root == null) createRoot(); return root; } public XModelObject getRoot(String name) { if(name == null || name.length() == 0) return getRoot(); XMapping m = getMetaData().getMapping("Roots"); //$NON-NLS-1$ String path = m.getValue(name); return (path == null || path.length() == 0) ? null : getRoot().getChildByPath(path); } void createRoot() { root = createModelObject(rootEntity, new Properties()); } public XModelObject getByPath(String path) { if(path == null || path.length() == 0) return getRoot(); if(path.startsWith("root:")) { //$NON-NLS-1$ int i = path.indexOf('/'); if(i < 0) return extraroots.get(path); XModelObject r = extraroots.get(path.substring(0, i)); return (r == null) ? null : r.getChildByPath(path.substring(i + 1)); } if(path.startsWith(XModelObjectConstants.SEPARATOR)) { return getByPathInFileSystem(path.substring(1)); } else if(path.startsWith("%")) { //$NON-NLS-1$ int i = path.indexOf("%"), j = path.lastIndexOf("%"); //$NON-NLS-1$ //$NON-NLS-2$ if(i == j) return null; String rt = path.substring(i + 1, j); XModelObject ro = getRoot(rt); return (ro == null || j + 2 >= path.length()) ? ro : ro.getChildByPath(path.substring(j + 2)); } else { return getRoot().getChildByPath(path); } } /* * If relative path goes into a folder overlapping a file system * the path is changed to relative with respect to that file system. */ public static XModelObject getByRelativePath(XModel model, String path) { XModelObject o = null; if(path != null && path.startsWith(XModelObjectConstants.SEPARATOR)) { XModelObject wr = FileSystemsHelper.getWebRoot(model); if(wr != null) o = wr.getChildByPath(path.substring(1)); } if(o == null) o = model.getByPath(path); if(o == null || !path.startsWith(XModelObjectConstants.SEPARATOR)) return o; XModelObject p = o; while(p != null && !XModelObjectConstants.TRUE.equals(p.get("overlapped"))) p = p.getParent(); //$NON-NLS-1$ if(p == null) return o; String newPath = o.getPath().substring(p.getPath().length()); if(path.equals(newPath)) { return o; } path = o.getPath().substring(p.getPath().length()); if(p.getModelEntity().getName().equals("FileFolder")) { //$NON-NLS-1$ XModelObject fs = findMountedFileSystem(p); if(fs != null && path.length() > 1) { XModelObject c = fs.getChildByPath(path.substring(1)); if(c != null) return c; } } return getByRelativePath(model, path); } static XModelObject findMountedFileSystem(XModelObject folder) { String s = XModelObjectLoaderUtil.getResourcePath(folder); if(s == null) return null; XModelObject p = folder.getParent(); while(p != null && p.getFileType() != XModelObject.SYSTEM) p = p.getParent(); if(!(p instanceof FileSystemImpl)) return null; String loc = ((FileSystemImpl)p).getAbsoluteLocation().replace('\\', '/'); XModelObject fs = FileSystemsHelper.getFileSystems(folder.getModel()); XModelObject[] cs = fs.getChildren(XModelObjectConstants.ENT_FILE_SYSTEM_FOLDER); for (int i = 0; i < cs.length; i++) { if(!(cs[i] instanceof FileSystemImpl)) continue; String loci = ((FileSystemImpl)cs[i]).getAbsoluteLocation().replace('\\', '/'); if((loc + s).equals(loci)) return cs[i]; } return null; } public XModelObject getByPathInFileSystem(String path) { XModelObject fs = getByPath(FileSystemsHelper.FILE_SYSTEMS); if(fs == null) return null; XModelObject[] cs = fs.getChildren(); for (int i = 0; i < cs.length; i++) { XModelObject o = cs[i].getChildByPath(path); if(o != null) return o; } return null; } static Set<String> unknownEntities = new HashSet<String>(); static void creationFailed(String entity, String cause) { if(!unknownEntities.contains(entity)) { unknownEntities.add(entity); String message = NLS.bind(cause, new Object[]{entity}); ModelPlugin.getPluginLog().logInfo(message); } } public XModelObject createModelObject(String entity, Properties properties) { if(unknownEntities.contains(entity)) { return null; } XModelEntity ent = getMetaData().getEntity(entity); if(ent == null) { creationFailed(entity, ModelMessages.UNKNOUN_ENTITY); return null; } XModelObjectImpl me = (XModelObjectImpl)ent.getObjectImplementation(); if(me == null) { creationFailed(entity, ModelMessages.CREATION_ENTITY_FAILURE); return null; } me.setModel(this); me.setEntityName_0(entity); XAttribute[] an = ent.getAttributes(); for (int i = 0; i < an.length; i++) { String n = an[i].getName(); String v = an[i].getDefaultValue(); me.set_0(n, v); if(properties != null) { v = properties.getProperty(n); if(v != null) me.set_0(n, v); } } return me; } public void editObjectAttribute(XModelObject object, String attributeName, String value) throws XModelException { changeObjectAttribute(object, attributeName, value, true); } public void changeObjectAttribute(XModelObject object, String attributeName, String value) throws XModelException { changeObjectAttribute(object, attributeName, value, false); } /** * Returns validation error. * @param object * @param attributeName * @param value * @return */ public String getError(XModelObject object, String attributeName, String value) { if(object == null || object.getPath() == null) return null; XModelEntity ent = object.getModelEntity(); XAttribute a = ent.getAttribute(attributeName); if(a == null) return null; if(a.isTrimmable() && value != null) value = value.trim(); String ov = object.getAttributeValue(attributeName); ov = (ov == null) ? "" : ov; //$NON-NLS-1$ if(value.length() == 0 && XModelObjectConstants.TRUE.equals(a.getProperty("required"))) { //$NON-NLS-1$ String mes = MessageFormat.format("Attribute {0} is required.", a.getName()); return mes; } XAttributeConstraint c = a.getConstraint(); if(c != null && service != null) { String error = (c instanceof XAttributeConstraintV) ? ((XAttributeConstraintV)c).getError(value, object) : c.getError(value); if(error != null) { String mes = NLS.bind(ModelMessages.SET_ATTRIBUTE_FAILURE, new Object[]{attributeName, value, error}); return mes; } } return null; } void changeObjectAttribute(XModelObject object, String attributeName, String value, boolean edit) throws XModelException { if(object == null || object.getPath() == null) return; XModelEntity ent = object.getModelEntity(); XAttribute a = ent.getAttribute(attributeName); if(a == null) return; if(a.isTrimmable() && value != null) value = value.trim(); String ov = object.getAttributeValue(attributeName); ov = (ov == null) ? "" : ov; //$NON-NLS-1$ if(!isDifferent(ov, value)) return; if(value.length() == 0 && XModelObjectConstants.TRUE.equals(a.getProperty("required"))) { //$NON-NLS-1$ String mes = MessageFormat.format("Attribute {0} is required.", a.getName()); service.showDialog("Error", mes, new String[]{"OK"}, null, ServiceDialog.ERROR); return; } XAttributeConstraint c = a.getConstraint(); if(c != null && service != null) { String error = (c instanceof XAttributeConstraintV) ? ((XAttributeConstraintV)c).getError(value, object) : c.getError(value); if(error != null) { if(edit) { String mes = NLS.bind(ModelMessages.SET_ATTRIBUTE_FAILURE, new Object[]{attributeName, value, error}); if(service != null) { XEntityData data = XEntityDataImpl.create(new String[][]{ {object.getModelEntity().getName(), XModelObjectConstants.YES}, {attributeName, XModelObjectConstants.NO} }); data.setValue(attributeName, value); int q = service.showDialog(ModelMessages.Error, mes, new String[]{ModelMessages.Finish, ModelMessages.Cancel}, data, ServiceDialog.ERROR); if(q != 0) return; changeObjectAttribute(object, attributeName, data.getValue(attributeName), true); } return; } else { value = c.getCorrectedValue(value); if(value == null) return; if(!isDifferent(ov, value)) return; } } } String nv = object.setAttributeValue(attributeName, value); nv = (nv == null) ? "" : nv; //$NON-NLS-1$ ent.setDependentValues(object, attributeName); XChangeUndo cu = new XChangeUndo(object, attributeName, ov); undoer.addUndoable(cu); object.setModified(true); if(edit) { ((XModelObjectImpl)object).onAttributeValueEdit(attributeName, ov, nv); } XModelObject f = FileSystemsHelper.getFile(object); if(f instanceof AbstractExtendedXMLFileImpl) { ((AbstractExtendedXMLFileImpl)f).check(); } // ent.getAttribute(attributeName).valueChanged(object); } private boolean isDifferent(String v1, String v2) { if(v1 == null) return v2 != null; if(v1.equals(v2)) return false; int i1 = -1, i2 = -1, l1 = v1.length(), l2 = v2.length(); char c1 = '\0', c2 = '\0'; while(i1 < l1 || l2 < l2) { if(i1 < l1) { ++i1; while(i1 < l1 && (c1 = v1.charAt(i1)) == '\r') ++i1; if(i1 == l1) c1 = '\0'; } if(i2 < l2) { ++i2; while(i2 < l2 && (c2 = v2.charAt(i2)) == '\r') ++i2; if(i2 == l2) c2 = '\0'; } if(c1 != c2) return true; } return false; } // listeners public synchronized void addModelTreeListener(XModelTreeListener listener) { if(treeListeners.contains(listener)) return; treeListeners.add(listener); treeListenersArray = (XModelTreeListener[])treeListeners.toArray(new XModelTreeListener[0]); } public synchronized void removeModelTreeListener(XModelTreeListener listener) { if(!treeListeners.contains(listener)) return; treeListeners.remove(listener); treeListenersArray = (XModelTreeListener[])treeListeners.toArray(new XModelTreeListener[0]); } // load - save - update public void load() { validateRootEntity(); ArrayList<XModelTreeListener> tls = treeListeners; treeListenersArray = new XModelTreeListener[0]; treeListeners = new ArrayList<XModelTreeListener>(); XModelObject r = getRoot(); XModelObjectLoaderUtil.getObjectLoader(r).load(r); treeListeners = tls; treeListenersArray = treeListeners.toArray(new XModelTreeListener[0]); undoer.setModel(this); undoer.reset(); fireStructureChanged(getRoot(), 3, null); ///Project Watcher if(!isDummy()) loadWatcher(); } ////ProjectWatcher private void loadWatcher() { if(getProperties().get(XModelObjectConstants.PROJECT) == null) return; XObjectLoader l = (XObjectLoader)ModelFeatureFactory.getInstance().createFeatureInstance("org.jboss.tools.common.model.project.WatcherLoader"); //$NON-NLS-1$ XModelObject fs = getByPath(FileSystemsHelper.FILE_SYSTEMS); if(l != null && fs != null) l.load(fs); } public boolean isDummy() { return "RootDummy".equals(getRoot().getModelEntity().getName()); //$NON-NLS-1$ } private String reduceURLPath(String p) { return FileUtil.fileURLToFilePath(p); } private String getProjectName() { String d = properties.getProperty(XModelConstants.WORKSPACE); String n = null; //obsolete if(d == null) return null; d = reduceURLPath(d); if(d.lastIndexOf(':') >= 2) return (n != null) ? n : d.substring(d.lastIndexOf('/') + 1); File f = null; f = new File(d); if(!"WEB-INF".equals(f.getName())) { //Let it be as before so that not to affect models //that are not related to a web project. f.mkdirs(); return (f == null || !f.isDirectory()) ? null : (n != null) ? n : f.getName(); } else { //Do not create WEB-iNF for a web project. return (n != null) ? n : f.getName(); } } private void validateRootEntity() { XModelObjectImpl r = (XModelObjectImpl)getRoot(); String project = getProjectName(); if(project == null) { r.setEntityName_0("RootDummy"); //$NON-NLS-1$ } else { r.setAttributeValue("project name", project); //$NON-NLS-1$ r.setEntityName_0(rootEntity); } } public void update() throws XModelException { XModelObject r = getRoot(); XModelObjectLoaderUtil.getObjectLoader(r).update(r); } public void save() { XModelObject r = getRoot(); r.set("isSaveOn", XModelObjectConstants.TRUE); //$NON-NLS-1$ XModelObjectLoaderUtil.getObjectLoader(r).save(r); r.set("isSaveOn", ""); //$NON-NLS-1$ //$NON-NLS-2$ } public void saveOptions() { XModelObject o = getRoot("XStudio"); //$NON-NLS-1$ if(o != null) XModelObjectLoaderUtil.getObjectLoader(o).save(o); } public void setExtraRoot(XModelObject r) { String pathpart = r.getPathPart(); if(!pathpart.startsWith("root:")) throw new IllegalArgumentException("Not extra root: " + pathpart); //$NON-NLS-1$ //$NON-NLS-2$ extraroots.put(pathpart, r); } public void removeExtraRoot(String pathpart) { extraroots.remove(pathpart); } // fire public void fireNodeChanged(XModelObject object, String info) { fireNodeChanged(object, info, null); } void fireNodeChanged(XModelObject object, String info, Object details) { if(object.getModel() != this || !object.isActive()) return; final XModelTreeEvent event = new XModelTreeEvent(this, object, 0, info, details); XModelTreeListener[] ls = treeListenersArray; for (int i = 0; i < ls.length; i++) { final XModelTreeListener l = ls[i]; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { } public void run() throws Exception { l.nodeChanged(event); } }); } } public void fireStructureChanged(XModelObject object) { fireStructureChanged(object, XModelTreeEvent.STRUCTURE_CHANGED, null); } public void fireStructureChanged(XModelObject object, int kind, Object info) { if(object.getModel() != this || !object.isActive()) return; final XModelTreeEvent event = new XModelTreeEvent(this, object, kind, info); XModelTreeListener[] ls = treeListenersArray; for (int i = 0; i < ls.length; i++) { final XModelTreeListener l = ls[i]; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { } public void run() throws Exception { l.structureChanged(event); } }); } } protected Map<String,Object> managers = new HashMap<String,Object>(); public Object getManager(String id) { return managers.get(id); } public void addManager(String id, Object manager) { managers.put(id, manager); } public void removeManager(String id) { managers.remove(id); } int loaderCount = 0; Object loaderMonitor = new Object(); public void addLoader() { synchronized(loaderMonitor) { loaderCount++; } } public void removeLoader() { synchronized(loaderMonitor) { loaderCount--; if(loaderCount <= 0) loaderMonitor.notifyAll(); } } public void waitForLoading() { synchronized(loaderMonitor) { if(loaderCount <= 0) return; try { loaderMonitor.wait(); } catch (InterruptedException e) { //ignore return; } } } public List<XModelTreeListener> getTreeListeners() { return Collections.unmodifiableList(this.treeListeners); } public Map<String,Object> getManagerMap() { return Collections.unmodifiableMap(managers); } }