/******************************************************************************* * Copyright (c) 2007, 2013 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are 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: * VTT Technical Research Centre of Finland - initial API and implementation * Semantum Oy - issue #4384 *******************************************************************************/ package org.simantics.ui.workbench; import java.lang.ref.Reference; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPersistableElement; import org.simantics.Simantics; import org.simantics.db.AsyncRequestProcessor; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.common.ResourceArray; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.exception.AdaptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ResourceNotFoundException; import org.simantics.db.layer0.exception.MissingVariableException; import org.simantics.db.layer0.variable.RVI; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; import org.simantics.db.request.Read; import org.simantics.db.service.LifecycleSupport; import org.simantics.ui.icons.ImageDescriptorProvider; import org.simantics.ui.workbench.editor.input.ResourceEditorInputMatchingStrategy; import org.simantics.utils.ObjectUtils; import org.simantics.utils.datastructures.cache.ProvisionException; import org.simantics.utils.ui.ErrorLogger; import org.simantics.utils.ui.workbench.StringMemento; /** * This is an input class for editors that have as their input the a tuple: * (Input Resource, Model URI, RVI). * * Editor extensions requiring these as input should always use * {@link ResourceEditorInputMatchingStrategy} as their matchingStrategy. * * @author Tuukka Lehtonen * @see ResourceEditorInput * @see ResourceEditorInputMatchingStrategy */ public class ResourceEditorInput2 extends PlatformObject implements IResourceEditorInput2, IPersistableElement { private final static boolean DEBUG_EXISTS = false; private final static boolean DEBUG_UPDATE = false; private static final String NO_NAME = ResourceEditorInput.NO_NAME; private final String editorID; /** * A random access ID to {@link #resource}. */ protected String modelId; protected String rvi; /** * A random access ID to {@link #resource}. */ private String resourceId; private transient Reference model; private transient Reference resource; private transient boolean exists; private transient String name; private transient String tooltip; private transient ImageDescriptor imageDesc; /** Persistent memento for external data */ private final StringMemento persistentStore = new StringMemento(); ResourceEditorInput2(String editorID, String resourceId, String modelId, String rvi) { if (editorID == null) throw new IllegalArgumentException("null editor id"); if (resourceId == null) throw new IllegalArgumentException("null resource id"); this.editorID = editorID; this.resourceId = resourceId; this.resource = null; this.modelId = modelId; this.model = null; this.rvi = rvi; setNonExistant(); } /** * @param editorID * @param resourceId * @param modelURI * @param rvi */ public ResourceEditorInput2(String editorID, String resourceId, String modelId, RVI rvi) { if (editorID == null) throw new IllegalArgumentException("null editor id"); if (resourceId == null) throw new IllegalArgumentException("null resource id"); this.editorID = editorID; this.resourceId = resourceId; this.resource = null; this.modelId = modelId; this.model = null; this.rvi = rvi.toString(); setNonExistant(); } @Deprecated public ResourceEditorInput2(String editorID, Resource resource, Resource model, String rvi) { if (editorID == null) throw new IllegalArgumentException("null editor id"); if (resource == null) throw new IllegalArgumentException("null resource"); if (model == null) throw new IllegalArgumentException("null model"); this.editorID = editorID; this.resourceId = ResourceInputs.getRandomAccessId(resource); this.resource = ResourceInputs.makeReference(resource); this.modelId = ResourceInputs.getRandomAccessId(model); this.model = ResourceInputs.makeReference(model); this.rvi = rvi; setNonExistant(); } public ResourceEditorInput2(String editorID, Resource resource, Resource model, RVI rvi) { if (editorID == null) throw new IllegalArgumentException("null editor id"); if (resource == null) throw new IllegalArgumentException("null resource"); if (model == null) throw new IllegalArgumentException("null model"); this.editorID = editorID; this.resourceId = ResourceInputs.getRandomAccessId(resource); this.resource = ResourceInputs.makeReference(resource); this.modelId = ResourceInputs.getRandomAccessId(model); this.model = ResourceInputs.makeReference(model); this.rvi = rvi != null ? rvi.toString() : null; setNonExistant(); } @Override public void init(IAdaptable adapter) throws DatabaseException { Resource r = getResource(); if (r != null) updateCaches(getSession(), true); } @Override public void dispose() { //System.out.println("dispose resource editor input: " + name); // NOTE: this has to be done since Eclipse will cache these IEditorInput // instances within EditorHistoryItem's that are stored in an EditorHistory // instance. They are held by strong reference which means that the session // cannot be collected if it is not nulled here. resource = null; model = null; } /** * @return a graph instance if it exists and has not yet been disposed, * null otherwise */ public Session getSession() { Session s = Simantics.getSession(); if (s.getService(LifecycleSupport.class).isClosed()) throw new IllegalStateException("database session is closed"); return s; } @Override public boolean exists() { return exists; } @Override public boolean exists(ReadGraph graph) throws DatabaseException { try { assertExists(graph); return true; } catch (MissingVariableException e) { } catch (ResourceNotFoundException e) { } catch (Nonexistant e) { } return false; } public Resource getResource0() throws DatabaseException { Resource r = tryGetResource(); if (r != null) return r; Session s = ResourceInputs.peekSession(); if (s == null) return null; r = ResourceInputs.resolveResource( s, resourceId ); this.resource = ResourceInputs.makeReference( r ); return r; } public Resource getModel0() throws DatabaseException { Resource r = tryGetModel(); if (r != null) return r; Session s = ResourceInputs.peekSession(); if (s == null) return null; r = ResourceInputs.resolveResource( s, modelId ); this.model = ResourceInputs.makeReference( r ); return r; } @Override public Resource getResource() { try { return getResource0(); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); return null; } } @Override @Deprecated public ResourceArray getResourceArray() { Resource r = getResource(); return r == null ? ResourceArray.EMPTY : new ResourceArray(r); } public Resource getModel(ReadGraph graph) { try { return getModel0(); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); return null; } } @Override public String getRVI() { return rvi; } /* (non-Javadoc) * @see org.eclipse.ui.IEditorInput#getImageDescriptor() */ @Override public ImageDescriptor getImageDescriptor() { return imageDesc; } /* (non-Javadoc) * @see org.eclipse.ui.IEditorInput#getName() */ @Override public String getName() { return name; } /* (non-Javadoc) * @see org.eclipse.ui.IEditorInput#getToolTipText() */ @Override public String getToolTipText() { return tooltip; } /* (non-Javadoc) * @see org.eclipse.ui.IEditorInput#getPersistable() */ @Override public IPersistableElement getPersistable() { // Don't allow persistability when it's not possible. if (!isPersistable()) return null; return this; } protected boolean isPersistable() { Session session = Simantics.peekSession(); if (session == null) return false; LifecycleSupport lc = session.peekService(LifecycleSupport.class); if (lc == null) return false; if (lc.isClosed()) return false; return true; } /* (non-Javadoc) * @see org.eclipse.ui.IPersistableElement#getFactoryId() */ @Override public String getFactoryId() { return ResourceEditorInputFactory2.getFactoryId(); } /** * Saves the state of the given resource editor input into the given memento. * * @param memento the storage area for element state * @see org.eclipse.ui.IPersistable#saveState(org.eclipse.ui.IMemento) */ @Override public void saveState(IMemento memento) { IMemento child = memento.createChild(ResourceEditorInputFactory2.TAG_RESOURCE_ID); child.putTextData(resourceId); memento.putString(ResourceEditorInputFactory2.TAG_EDITOR_ID, editorID); memento.putString(ResourceEditorInputFactory2.TAG_MODEL_ID, modelId); memento.putString(ResourceEditorInputFactory2.TAG_RVI, rvi); memento.putString(ResourceEditorInputFactory2.TAG_EXTERNAL_MEMENTO_ID, persistentStore.toString()); } /* (non-Javadoc) * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ @Override public T getAdapter(Class adapter) { //System.out.println("[ResourceEditorInput] getAdapter: " + adapter.getName()); return null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + editorID.hashCode(); // modelURI and rvi may change => can't use either in hashcode. // result = prime * result + ObjectUtils.hashCode(modelURI); // result = prime * result + ObjectUtils.hashCode(rvi); result = prime * result + ObjectUtils.hashCode(modelId); result = prime * result + ObjectUtils.hashCode(resourceId); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ResourceEditorInput2 other = (ResourceEditorInput2) obj; if (!editorID.equals(other.editorID)) return false; if (!ObjectUtils.objectEquals(modelId, other.modelId)) return false; if (!ObjectUtils.objectEquals(rvi, other.rvi)) return false; if (!ObjectUtils.objectEquals(resourceId, other.resourceId)) return false; return true; } private void updateCaches(AsyncRequestProcessor processor, boolean sync) throws DatabaseException { ReadRequest req = new ReadRequest() { @Override public void run(ReadGraph g) throws DatabaseException { update(g); } }; if (sync) { processor.syncRequest(req); } else { processor.asyncRequest(req); } } static class Nonexistant extends DatabaseException { private static final long serialVersionUID = -7964385375237203651L; @Override public synchronized Throwable fillInStackTrace() { return this; } } /* (non-Javadoc) * @see org.simantics.ui.workbench.IResourceEditorInput#update(org.simantics.db.Graph) */ @Override public void update(ReadGraph g) throws DatabaseException { Resource r = getResource(); if (r == null) return; if (DEBUG_UPDATE) System.out.println("update(" + this + ")"); try { //assertExists(g); name = g.syncRequest(new TitleRequest(editorID, this)); if (name == null) name = NO_NAME; tooltip = g.syncRequest(new ToolTipRequest(editorID, this)); if (tooltip == null) tooltip = NO_NAME; try { ImageDescriptorProvider idp = g.adapt(r, ImageDescriptorProvider.class); imageDesc = idp.get(); } catch (AdaptionException e) { imageDesc = ImageDescriptor.getMissingImageDescriptor(); } catch (ProvisionException e) { imageDesc = ImageDescriptor.getMissingImageDescriptor(); ErrorLogger.defaultLogError(e); } if (DEBUG_UPDATE) System.out.println("update(" + this + ") finished"); } catch (DatabaseException e) { if (DEBUG_UPDATE) e.printStackTrace(); setNonExistant(); } } private void assertExists(ReadGraph g) throws DatabaseException { if (DEBUG_EXISTS) System.out.println("ResourceEditorInput2.assertExists(" + this + ") begins"); // 1. Check resource existence Resource r = getResource(); if (r == null) throw new Nonexistant(); exists = g.hasStatement(r); if (!exists) throw new Nonexistant(); // 2. Check model existence Resource model = getModel(g); if (model == null) throw new Nonexistant(); exists = g.hasStatement(model); if (!exists) throw new Nonexistant(); // 3. Validate rvi if (DEBUG_EXISTS) System.out.println("validating rvi: '" + rvi + "'"); if(rvi != null && !rvi.isEmpty()) { Variable context = Variables.getPossibleConfigurationContext(g, model); if (context == null) throw new Nonexistant(); RVI rvi_ = RVI.fromResourceFormat(g, rvi); Variable variable = rvi_.resolvePossible(g, context); if (variable == null) throw new Nonexistant(); } // Touch the diagram title calculation within this existence // checking request. g.syncRequest(new TitleRequest(editorID, this)); if (DEBUG_EXISTS) System.out.println("ResourceEditorInput2.assertExists(" + this + ") finished"); } private void setNonExistant() { if (DEBUG_UPDATE) System.out.println("setNonExistant(" + this + " @ " + System.identityHashCode(this) + ")"); exists = false; tooltip = name = NO_NAME; imageDesc = ImageDescriptor.getMissingImageDescriptor(); } public IMemento getPersistentStore() { return persistentStore; } @Override public String toString() { return getClass().getSimpleName() + " [name=" + getName() + ", resource=" + resource + ", model=" + model + ", rvi=" + rvi + "]"; } /** * @see org.simantics.ui.workbench.IResourceEditorInput2#getVariable() */ public Variable getVariable() throws DatabaseException { return getSession().syncRequest(new Read() { @Override public Variable perform(ReadGraph graph) throws DatabaseException { return getVariable(graph); } }); } /** * @see org.simantics.ui.workbench.IResourceEditorInput2#getVariable(org.simantics.db.ReadGraph) */ public Variable getVariable(ReadGraph graph) throws DatabaseException { Resource model = getModel(graph); String rvi = getRVI(); // Model + RVI if (rvi != null) { Variable configuration = Variables.getConfigurationContext(graph, model); RVI rrvi = RVI.fromResourceFormat(graph, rvi); return rrvi.resolve(graph, configuration); } // Absolute URI else { return Variables.getVariable(graph, model); } } private Resource tryGetResource() { Reference ref = resource; return ref == null ? null : ref.get(); } private Resource tryGetModel() { Reference ref = model; return ref == null ? null : ref.get(); } }