/***************************************************************************** * Copyright (c) 2006-2009 g-Eclipse Consortium * 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 * * Initial development of the original code was made for the * g-Eclipse project founded by European Union * project number: FP6-IST-034327 http://www.geclipse.eu/ * * Contributors: * Mathias Stuempert - initial API and implementation * Ariel Garcia - updated to new problem reporting *****************************************************************************/ package eu.geclipse.core.model.impl; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IResource; 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.eclipse.core.runtime.jobs.Job; import eu.geclipse.core.ICoreProblems; import eu.geclipse.core.auth.AbstractAuthTokenProvider; import eu.geclipse.core.internal.Activator; import eu.geclipse.core.internal.model.GridRoot; import eu.geclipse.core.internal.model.notify.GridModelEvent; import eu.geclipse.core.internal.model.notify.GridNotificationService; import eu.geclipse.core.model.IGridContainer; import eu.geclipse.core.model.IGridElement; import eu.geclipse.core.model.IGridElementCreator; import eu.geclipse.core.model.IGridElementManager; import eu.geclipse.core.model.IGridModelEvent; import eu.geclipse.core.model.IGridModelListener; import eu.geclipse.core.model.IManageable; import eu.geclipse.core.reporting.ProblemException; import eu.geclipse.core.util.MasterMonitor; /** * Base implementation of the {@link IGridContainer} interface that * implements basic functionalities of a grid container. */ public abstract class AbstractGridContainer extends AbstractGridElement implements IGridContainer { private static class ChildFetcher extends Job { private final AbstractGridContainer container; private IProgressMonitor externalMonitor; private Throwable exception; /** * true if this cancel was called for that fetcher but run() hasn't finished * yet */ private boolean canceling; /** * Construct a new child fetcher for the specified container. * * @param container The container whose children should be fetched. */ public ChildFetcher( final AbstractGridContainer container ) { super( "Child Fetcher @ " + container.getName() ); //$NON-NLS-1$ this.container = container; } /** * Get an exception that occurred during child fetching or <code>null</code> * of no such exception occurred. * * @return The exception of <code>null</code> if either the fetcher did not * yet run or no exception occurred. */ public Throwable getException() { return this.exception; } /** * True if this fetcher has not yet run, i.e. it is currently scheduled, or * if it currently runs. * * @return True if the job has not yet finished. */ public boolean isFetching() { return getState() != NONE; } /** * Set a progress monitor that is used in the run method in parallel with * the monitor provided by the run method parameter. * * @param monitor The external monitor. */ public void setExternalMonitor( final IProgressMonitor monitor ) { this.externalMonitor = monitor; } /* * (non-Javadoc) * @seeorg.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime. * IProgressMonitor) */ @Override protected IStatus run( final IProgressMonitor monitor ) { IProgressMonitor mon = new MasterMonitor( monitor, this.externalMonitor ); this.canceling = false; this.exception = null; this.container.lock(); try { this.container.deleteAll(); IStatus status = this.container.fetchChildren( mon ); if( !status.isOK() ) { Throwable exc = status.getException(); if ( ! AbstractAuthTokenProvider.isTokenRequestCanceledException( exc ) ) { this.exception = status.getException(); } } } catch( Throwable t ) { this.exception = t; } finally { if( !this.canceling ) { this.container.setDirty( false ); } this.container.unlock(); this.canceling = false; } return Status.OK_STATUS; } @Override protected void canceling() { this.canceling = true; super.canceling(); } public boolean isCanceling() { return this.canceling; } } /** * List of currently know children. */ private final List< IGridElement > children = new ArrayList< IGridElement >(); /** * Dirty flag of this container. */ private boolean dirty; /** * Job used internally for fetching the containers children. */ private ChildFetcher fetcher; protected AbstractGridContainer() { setDirty(); } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#canContain(eu.geclipse.core.model.IGridElement) */ public boolean canContain( final IGridElement element ) { return false; } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#contains(eu.geclipse.core.model.IGridElement) */ public boolean contains( final IGridElement element ) { return this.children.contains( element ); } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#create(eu.geclipse.core.model.IGridElementCreator) */ public IGridElement create( final IGridElementCreator creator ) throws ProblemException { IGridElement element = creator.create( this ); element = addElement( element ); return element; } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#delete(eu.geclipse.core.model.IGridElement) */ public void delete( final IGridElement child ) throws ProblemException { removeElement( child ); unregisterFromManager( child ); child.dispose(); } private void unregisterFromManager( final IGridElement child ) { if ( child instanceof IManageable ) { IGridElementManager manager = ( ( IManageable ) child ).getManager(); manager.removeElement( child ); } } /* (non-Javadoc) * @see eu.geclipse.core.model.impl.AbstractGridElement#dispose() */ @Override public void dispose() { deleteAll(); super.dispose(); } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#getChildCount() */ public int getChildCount() { int result; if ( isLazy() && isDirty() ) { result = 1; } else { result = this.children.size(); } return result; } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#getChildren(org.eclipse.core.runtime.IProgressMonitor) */ public IGridElement[] getChildren( final IProgressMonitor monitor ) throws ProblemException { if ( isLazy() && isDirty() ) { try { startFetch( monitor ); } catch ( Throwable t ) { if ( t instanceof ProblemException ) { throw ( ProblemException ) t; } throw new ProblemException( ICoreProblems.MODEL_FETCH_CHILDREN_FAILED, t, Activator.PLUGIN_ID ); } } return this.children.toArray( new IGridElement[ this.children.size() ] ); } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#hasChildren() */ public boolean hasChildren() { return isLazy() || !this.children.isEmpty(); } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#findChild(java.lang.String) */ public IGridElement findChild( final String name ) { IGridElement result = null; for ( IGridElement child : this.children ) { if ( child.getName().equals( name ) ) { result = child; break; } } return result; } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#findChildWithResource(java.lang.String) */ public IGridElement findChildWithResource( final String resourceName ) { IGridElement result = null; for ( IGridElement child : this.children ) { if ( !child.isVirtual() ) { if ( child.getResource().getName().equals( resourceName ) ) { result = child; break; } } } return result; } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#isDirty() */ public boolean isDirty() { return this.dirty; } public void refresh( final IProgressMonitor monitor ) throws ProblemException { if ( ! isVirtual() ) { IContainer container = ( IContainer ) getResource(); try { lock(); container.refreshLocal( IResource.DEPTH_INFINITE, monitor ); } catch( CoreException cExc ) { throw new ProblemException( ICoreProblems.MODEL_REFRESH_FAILED, cExc, Activator.PLUGIN_ID ); } finally { unlock(); } } else { setDirty(); try { startFetch( monitor ); } catch ( Throwable t ) { if ( t instanceof ProblemException ) { throw ( ProblemException ) t; } throw new ProblemException( ICoreProblems.MODEL_REFRESH_FAILED, t, Activator.PLUGIN_ID ); } } } /* (non-Javadoc) * @see eu.geclipse.core.model.IGridContainer#setDirty() */ public void setDirty() { setDirty( true ); } /** * Add an element as child to this container. If a child with the same * name is already contained in this contained this old child will * be deleted. * * @param element The new child of this container or <code>null</code> * if an error occurs. * @return The newly added element. */ protected IGridElement addElement( final IGridElement element ) throws ProblemException { if ( element != null ) { testCanContain( element ); IGridElement oldChild = findChild( element.getName() ); if ( oldChild != null ) { delete( oldChild ); } this.children.add( element ); GridRoot.registerElement( element ); fireGridModelEvent( IGridModelEvent.ELEMENTS_ADDED, element ); if ( isLazy() && ! ( element instanceof ContainerMarker ) ) { for ( IGridElement child : this.children ) { if ( child instanceof ContainerMarker ) { removeElement( child ); break; } } } } return element; } /** * Remove all children from this container and call their * {@link #dispose()} methods. */ public void deleteAll() { if ( this.children != null && !this.children.isEmpty() ) { for ( IGridElement child : this.children ) { unregisterFromManager( child ); child.dispose(); } IGridElement[] elements = this.children.toArray( new IGridElement[ this.children.size() ] ); fireGridModelEvent( IGridModelEvent.ELEMENTS_REMOVED, elements ); this.children.clear(); } } /** * Fetch the children of this container. For a non-lazy container * the children are fetched when the container is constructed. For * lazy containers the children are fetched by the * {@link #getChildren(IProgressMonitor)} method if the container is * dirty. * * @param monitor A progress monitor to monitor the progress of this * maybe long running method. * @return True if the operation was successful. */ @SuppressWarnings("unused") protected IStatus fetchChildren( final IProgressMonitor monitor ) throws ProblemException { return Status.OK_STATUS; } protected void removeElement( final IGridElement element ) throws ProblemException { boolean result = this.children.remove( element ); if ( result ) { fireGridModelEvent( IGridModelEvent.ELEMENTS_REMOVED, element ); if ( this.children.isEmpty() && isLazy() && ! ( element instanceof ContainerMarker ) ) { ContainerMarker marker = ContainerMarker.getEmptyFolderMarker( this ); if ( canContain( marker ) ) { addElement( marker ); } } } } /** * Set the dirty flag of this container. If setting to dirty, the * flags of all child containers are also set recursively to dirty. * * @param d The new value of the container's dirty flag. */ protected void setDirty( final boolean d ) { this.dirty = d; if ( d ) { if ( this.fetcher != null && this.fetcher.isFetching() ) { this.fetcher.cancel(); } List< IGridElement > synchronizedList = Collections.synchronizedList( this.children ); synchronized ( synchronizedList ) { for ( IGridElement child : synchronizedList ) { if ( child instanceof IGridContainer ) { ( ( IGridContainer ) child ).setDirty(); } } } } } protected void lock() { getGridNotificationService().lock( this ); } protected void unlock() { getGridNotificationService().unlock( this ); } protected void fireGridModelEvent( final int type, final IGridElement element ) { fireGridModelEvent( type, new IGridElement[] { element } ); } protected void fireGridModelEvent( final int type, final IGridElement[] elements ) { if ( elements != null && elements.length > 0 ) { IGridModelEvent event = new GridModelEvent( type, this, elements ); getGridNotificationService().queueEvent( event ); } } static private GridNotificationService getGridNotificationService() { return GridNotificationService.getInstance(); } /** * To register IGridModelListener within constructor or static method, I cannot call GridRoot.getInstance(). * For reason @see bug #209160 * So instead of GridRoot, this method is used to register IGridModelListener * @param listener */ static protected void staticAddGridModelListener( final IGridModelListener listener ) { getGridNotificationService().addListener( listener ); } private void startFetch( final IProgressMonitor monitor ) throws Throwable { if ( this.fetcher == null ) { this.fetcher = new ChildFetcher( this ); } this.fetcher.setExternalMonitor( monitor ); // if canceling, then schedule again (don't wait for finish cancelation - scheduler start job again). if ( ! this.fetcher.isFetching() || this.fetcher.isCanceling() ) { this.fetcher.schedule(); } try { this.fetcher.join(); } catch ( InterruptedException intExc ) { // Silently ignored } Throwable exc = this.fetcher.getException(); if ( exc != null ) { throw exc; } } /** * Test if this container can contain the specified element * and throw a {@link ProblemException} if this is not the * case. * * @param element The element to be tested. * @throws ProblemException Thrown if {@link #canContain(IGridElement)} * returns false for the specified element. */ private void testCanContain( final IGridElement element ) throws ProblemException { if ( ! canContain( element ) ) { throw new ProblemException( ICoreProblems.MODEL_CONTAINER_CAN_NOT_CONTAIN, String.format( Messages.getString("AbstractGridContainer.can_not_contain_error"), //$NON-NLS-1$ getClass().getName(), element.getClass().getName() ), Activator.PLUGIN_ID ); } } /** * @return get current children elements. This method do not * fetchChildren, so for dirty container it return dirty (old) * children, for lazy container it may return empty list (even if * children really exists but weren't fetched yet). */ protected List<IGridElement> getCachedChildren() { return this.children; } }