project/com.nokia.carbide.cpp.epoc.engine/src/com/nokia/carbide/internal/cpp/epoc/engine/model/ModelProviderBase.java
changeset 0 fb279309251b
child 140 697b7727d088
equal deleted inserted replaced
-1:000000000000 0:fb279309251b
       
     1 /*
       
     2 * Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of the License "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description: 
       
    15 *
       
    16 */
       
    17 
       
    18 package com.nokia.carbide.internal.cpp.epoc.engine.model;
       
    19 
       
    20 import com.nokia.carbide.cpp.epoc.engine.DocumentFactory;
       
    21 import com.nokia.carbide.cpp.epoc.engine.EpocEnginePlugin;
       
    22 import com.nokia.carbide.cpp.epoc.engine.model.IModel;
       
    23 import com.nokia.carbide.cpp.epoc.engine.model.IModelFactory;
       
    24 import com.nokia.carbide.cpp.epoc.engine.model.IModelProvider;
       
    25 import com.nokia.carbide.cpp.epoc.engine.model.IOwnedModel;
       
    26 import com.nokia.cpp.internal.api.utils.core.*;
       
    27 
       
    28 import org.eclipse.core.runtime.CoreException;
       
    29 import org.eclipse.core.runtime.IPath;
       
    30 import org.eclipse.jface.text.IDocument;
       
    31 
       
    32 import java.util.Collection;
       
    33 import java.util.HashMap;
       
    34 import java.util.HashSet;
       
    35 import java.util.Iterator;
       
    36 import java.util.Map;
       
    37 import java.util.Set;
       
    38 
       
    39 /**
       
    40  * Base implementation of a model provider.  Real implementations
       
    41  * need to provide a mechanism for retrieving and saving file contents.
       
    42  *
       
    43  */
       
    44 public abstract class ModelProviderBase implements IModelProvider {
       
    45 
       
    46 	// not static or final so it can change during test
       
    47 	private boolean DUMP = false;
       
    48 	
       
    49 	private static int gProviderCounter;
       
    50 	private int providerId;
       
    51 	
       
    52 	private static final long RELEASE_DELAY = 5 * 1000;
       
    53 	private boolean cacheModels;
       
    54 	
       
    55 	// this acts as the lock on all the state managed by this object
       
    56 	private Object modelLock = new Object();
       
    57 	
       
    58 	private Map<IPath, IOwnedModel> models;
       
    59 	//private Map<IPath, IModelListener> modelListeners;
       
    60 	private Map<IPath, Integer> modelUseCount;
       
    61 	private Map<IPath, Long> releasedModelTimes;
       
    62 	private Map<IPath, IOwnedModel> releasedModels;
       
    63 	private Map<IPath, Set<IOwnedModel>> modelFileReferences;
       
    64 	protected IModelFactory modelFactory;
       
    65 
       
    66 	private long lastFlushTime;
       
    67 
       
    68 	public ModelProviderBase(IModelFactory modelFactory) {
       
    69 		providerId = ++gProviderCounter;
       
    70 		Check.checkArg(modelFactory);
       
    71 		this.cacheModels = true;
       
    72 		this.modelFactory = modelFactory;
       
    73 		this.modelUseCount = new HashMap<IPath, Integer>();
       
    74 		this.models = new HashMap<IPath, IOwnedModel>();
       
    75 		//this.modelListeners = new HashMap<IPath, IModelListener>();
       
    76 		this.releasedModelTimes = new HashMap<IPath, Long>();
       
    77 		this.releasedModels = new HashMap<IPath, IOwnedModel>();
       
    78 		this.modelFileReferences = new HashMap<IPath, Set<IOwnedModel>>();
       
    79 	}
       
    80 	
       
    81 	/** 
       
    82 	 * Get the canonical form of the path -- a full filesystem path.
       
    83 	 */
       
    84 	protected abstract IPath getFullPath(IPath path);
       
    85 	
       
    86 	/**
       
    87 	 * Load the text from the given full path, or return null
       
    88 	 * for a nonexistent file.
       
    89 	 * @param fullPath full path to resource
       
    90 	 * @return contents of file
       
    91 	 * @throws CoreException if the file could not be read,
       
    92 	 * for reasons other than not existing.
       
    93 	 */
       
    94 	protected abstract String loadStorage(IPath fullPath) throws CoreException;
       
    95 
       
    96 	/**
       
    97 	 * Save the text to the given full path, creating it and its parents if
       
    98 	 * necessary.  The implementation may ignore a save if the text does not differ.
       
    99 	 * @param path full path to resource
       
   100 	 * @param text the text
       
   101 	 */
       
   102 	protected abstract void saveStorage(IPath fullPath, String text) throws CoreException;
       
   103 
       
   104 	/**
       
   105 	 * Enable tracking for the storage underlying a registered model's contents.
       
   106 	 * This may be called multiple times for an already-registered path.
       
   107 	 * <p>
       
   108 	 * If changes are detected, {@link #fireStorageChanged()} should be called.
       
   109 	 * <p>
       
   110 	 * A call to {@link #stopTrackingStorage(IPath)} is made around approved
       
   111 	 * saves of documents, so implementors don't have to handle that.
       
   112 	 */
       
   113 	protected abstract void startTrackingStorage(IPath fullPath);
       
   114 
       
   115 	/**
       
   116 	 * Disable tracking for the storage underlying a registered model's contents.
       
   117 	 */
       
   118 	protected abstract void stopTrackingStorage(IPath fullPath);
       
   119 
       
   120 	/**
       
   121 	 * Called by implementors to notify that the storage at the given
       
   122 	 * path has changed.  This is called by the implementation
       
   123 	 * when changes to tracked storage can be detected with listeners.
       
   124 	 * @param path
       
   125 	 * @see #startTrackingStorage(IPath)
       
   126 	 * @see #stopTrackingStorage(IPath)
       
   127 	 */
       
   128 	protected void fireStorageChanged(IPath fullPath) {
       
   129 		IOwnedModel model = lookupSharedModel(fullPath);
       
   130 		if (model != null) {
       
   131 			// the model document itself was modified
       
   132 			refreshModel(fullPath, model);
       
   133 		} else {
       
   134 			// see if a header for one of the models was modified
       
   135 			Collection<IOwnedModel> referencingModels = modelFileReferences.get(fullPath);
       
   136 			if (referencingModels != null) {
       
   137 				for (IOwnedModel refModel : referencingModels) {
       
   138 					refreshModel(refModel.getPath(), refModel);
       
   139 				}
       
   140 			}
       
   141 		}
       
   142 	}
       
   143 
       
   144 	/**
       
   145 	 * Check the model's file for changes.  This acts as a polling solution for cases
       
   146 	 * where changes to tracked storage cannot be implemented with listeners. 
       
   147 	 * <p>
       
   148 	 * The implementation need only store modification information when files are
       
   149 	 * loaded and saved and double-check that information here.  This may return false
       
   150 	 * if the implementation reports changes with {@link #fireStorageChanged(IPath)}.
       
   151 	 * <p>
       
   152 	 * The implementation must not call {@link #fireStorageChanged(IPath)}.
       
   153 	 * @see #startTrackingStorage(IPath)
       
   154 	 * @see #stopTrackingStorage(IPath)
       
   155 	 */
       
   156 	protected abstract boolean hasTrackedStorageChanged(IPath fullPath);
       
   157 
       
   158 	public void updateModelDocumentMappings(IOwnedModel model) {
       
   159 		startTrackingModelStorage(model);
       
   160 	}
       
   161 	
       
   162 	/**
       
   163 	 * Start tracking storage for all the documents owned by the model.
       
   164 	 * @param model
       
   165 	 */
       
   166 	private void startTrackingModelStorage(IOwnedModel model) {
       
   167 		Map<IPath, IDocument> documentMap = model.getDocumentMap();
       
   168 		for (IPath path : documentMap.keySet()) {
       
   169 			Set<IOwnedModel> models = modelFileReferences.get(path);
       
   170 			if (models == null) {
       
   171 				models = new HashSet<IOwnedModel>();
       
   172 				modelFileReferences.put(path, models);
       
   173 			}
       
   174 			models.add(model);
       
   175 			startTrackingStorage(path);
       
   176 		}
       
   177 	}
       
   178 
       
   179 	/**
       
   180 	 * Start tracking storage for all the documents owned by the model.
       
   181 	 * @param model
       
   182 	 */
       
   183 	private void stopTrackingModelStorage(IOwnedModel model) {
       
   184 		Map<IPath, IDocument> documentMap = model.getDocumentMap();
       
   185 		for (IPath path : documentMap.keySet()) {
       
   186 			Set<IOwnedModel> models = modelFileReferences.get(path);
       
   187 			if (models != null) {
       
   188 				models.remove(model);
       
   189 				if (models.isEmpty()) {
       
   190 					modelFileReferences.remove(path);
       
   191 				}
       
   192 			}
       
   193 			stopTrackingStorage(path);
       
   194 		}
       
   195 	}
       
   196 
       
   197 	/**
       
   198 	 * Refresh the model's contents from storage, if changed.
       
   199 	 * @param fullPath
       
   200 	 * @param model
       
   201 	 */
       
   202 	private void refreshModel(IPath fullPath, IOwnedModel model) {
       
   203 		try {
       
   204 			// get updated file
       
   205 			String text = loadStorage(fullPath);
       
   206 			
       
   207 			// make model reload with new contents
       
   208 			String currentText = model.getDocument().get();
       
   209 			if (currentText == null || !currentText.equals(text)) {
       
   210 				model.getDocument().set(text != null ? text : ""); //$NON-NLS-1$
       
   211 				
       
   212 				// the change to the document should trigger a reparse
       
   213 				// and invalidation of any open views 
       
   214 			}
       
   215 			
       
   216 		} catch (CoreException e) {
       
   217 			EpocEnginePlugin.log(e);
       
   218 		}
       
   219 	}
       
   220 	
       
   221 	/* (non-Javadoc)
       
   222 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#createModel(org.eclipse.core.runtime.IPath)
       
   223 	 */
       
   224 	public IOwnedModel createModel(IPath somePath) {
       
   225 		IPath fullPath = getFullPath(somePath);
       
   226 		return modelFactory.createModel(fullPath, null);
       
   227 	}
       
   228 
       
   229 	/* (non-Javadoc)
       
   230 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#load(com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPOwnedModel)
       
   231 	 */
       
   232 	public void load(IOwnedModel model) throws CoreException {
       
   233 		String text = loadStorage(model.getPath());
       
   234 		IDocument document = model.getDocument() != null ? model.getDocument() : DocumentFactory.createDocument();
       
   235 		if (text != null)
       
   236 			document.set(text);
       
   237 		if (document != model.getDocument()) {
       
   238 			model.setDocument(document);
       
   239 		}
       
   240 	}
       
   241 	
       
   242 	/* (non-Javadoc)
       
   243 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#save(com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPOwnedModel)
       
   244 	 */
       
   245 	public void save(IOwnedModel model) throws CoreException {
       
   246 		save(model, model.getDocumentMap());
       
   247 	}
       
   248 	
       
   249 	/* (non-Javadoc)
       
   250 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#save(com.nokia.carbide.cpp.epoc.engine.model.mmp.IMMPOwnedModel)
       
   251 	 */
       
   252 	public void save(IOwnedModel model, Map documentMap_) throws CoreException {
       
   253 		Map<IPath, IDocument> documentMap = documentMap_;
       
   254 		
       
   255 		IPath path = model.getPath();
       
   256 		
       
   257 		if (models.containsKey(path)) {
       
   258 			stopTrackingModelStorage(model);
       
   259 		}
       
   260 		
       
   261 		// check for changed documents and save modified files only
       
   262 		for (Map.Entry<IPath, IDocument> entry : documentMap.entrySet()) {
       
   263 			saveStorage(entry.getKey(), entry.getValue().get());
       
   264 		}
       
   265 		
       
   266 		if (models.containsKey(path)) {
       
   267 			startTrackingModelStorage(model);
       
   268 		}
       
   269 	}
       
   270 	
       
   271 	/**
       
   272 	 * Register a model whose document has been established.
       
   273 	 * Called with modelLock held.
       
   274 	 * @param model
       
   275 	 * @throws CoreException
       
   276 	 */
       
   277 	private void doRegisterModel(IOwnedModel model) throws CoreException {
       
   278 		IPath path = model.getPath();
       
   279 		Check.checkArg(!models.containsKey(path));
       
   280 		if (DUMP) System.out.println(providerId+") " + "Registering "+ path); //$NON-NLS-1$ //$NON-NLS-2$
       
   281 		if (releasedModels.containsKey(path)) {
       
   282 			if (DUMP) System.out.println("--> registering released model"); //$NON-NLS-1$
       
   283 			releasedModels.remove(path);
       
   284 			releasedModelTimes.remove(path);
       
   285 		}
       
   286 		models.put(path, model);
       
   287 		model.setModelProvider(this);
       
   288 		
       
   289 		/*
       
   290 		IModelListener modelListener = new IModelListener() {
       
   291 		
       
   292 			public void modelChanged(IOwnedModel model) {
       
   293 			}
       
   294 
       
   295 			public void modelUpdated(IOwnedModel model, IView view) {
       
   296 				try {
       
   297 					save(model);
       
   298 				} catch (CoreException e) {
       
   299 					EpocEnginePlugin.log(e);
       
   300 				}
       
   301 			}
       
   302 		};
       
   303 		
       
   304 		model.addListener(modelListener);
       
   305 		modelListeners.put(path, modelListener);
       
   306 		*/
       
   307 		
       
   308 		// owner holds one reference
       
   309 		modelUseCount.put(path, new Integer(1));
       
   310 		
       
   311 		// ensure the model is fully set up
       
   312 		model.parse();
       
   313 		startTrackingModelStorage(model);
       
   314 	}
       
   315 
       
   316 	/* (non-Javadoc)
       
   317 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#registerModel(SharedModel)
       
   318 	 */
       
   319 	public void registerModel(IOwnedModel model) throws CoreException {
       
   320 		synchronized (modelLock) {
       
   321 			if (model.getDocument() != null) {
       
   322 				// create from initial contents
       
   323 				save(model);
       
   324 			} else {
       
   325 				// try to load, or make empty if not existing
       
   326 				load(model);
       
   327 			}
       
   328 	
       
   329 			doRegisterModel(model);
       
   330 		}
       
   331 	}
       
   332 
       
   333 	/* (non-Javadoc)
       
   334 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#unregisterModel(SharedModel)
       
   335 	 */
       
   336 	public void unregisterModel(IOwnedModel model) {
       
   337 		IPath path = model.getPath();
       
   338 		synchronized (modelLock) {
       
   339 			if (DUMP) System.out.println(providerId+") " + "Unregistering " + path); //$NON-NLS-1$ //$NON-NLS-2$
       
   340 			Check.checkArg(models.containsKey(path) || releasedModels.containsKey(path));
       
   341 			//model.removeListener(modelListeners.get(path));
       
   342 			//modelListeners.remove(path);
       
   343 			models.remove(path);
       
   344 			releasedModels.remove(path);
       
   345 			releasedModelTimes.remove(path);
       
   346 			model.setModelProvider(null);
       
   347 		}
       
   348 		stopTrackingModelStorage(model);
       
   349 	}
       
   350 
       
   351 	private IOwnedModel lookupSharedModel(IPath fullPath) {
       
   352 		IOwnedModel model;
       
   353 		if (DUMP) System.out.println(providerId+") " + "Finding shared model " + fullPath); //$NON-NLS-1$ //$NON-NLS-2$
       
   354 		// try to resurrect released model
       
   355 		model = releasedModels.get(fullPath);
       
   356 		if (model != null) {
       
   357 			if (DUMP) System.out.println("--> resurrecting"); //$NON-NLS-1$
       
   358 			models.put(fullPath, model);
       
   359 			releasedModels.remove(fullPath);
       
   360 			releasedModelTimes.remove(fullPath);
       
   361 		} else {
       
   362 			if (DUMP) System.out.println("--> looking up"); //$NON-NLS-1$
       
   363 			model = models.get(fullPath);
       
   364 		}
       
   365 		if (model != null) {
       
   366 			incrementUseCount(fullPath);
       
   367 			if (DUMP) System.out.println("--> found, refcount = " + testGetUseCount(model)); //$NON-NLS-1$
       
   368 		}
       
   369 		return model;
       
   370 	}
       
   371 	
       
   372 	public IModel findSharedModel(IPath somePath) {
       
   373 		if (somePath == null) {
       
   374 			return null;
       
   375 		}
       
   376 		IPath fullPath = getFullPath(somePath);
       
   377 		synchronized (modelLock) {
       
   378 			IOwnedModel model = lookupSharedModel(fullPath);
       
   379 			if (model != null && hasTrackedStorageChanged(fullPath)) {
       
   380 				refreshModel(fullPath, model);
       
   381 			}
       
   382 			return model;
       
   383 		}
       
   384 	}
       
   385 
       
   386 	/* (non-Javadoc)
       
   387 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#getSharedModel(org.eclipse.core.runtime.IPath, com.nokia.carbide.cpp.epoc.engine.model.IModelConfiguration)
       
   388 	 */
       
   389 	public IModel getSharedModel(IPath somePath) throws CoreException {
       
   390 		if (somePath == null) {
       
   391 			throw new CoreException(Logging.newStatus(EpocEnginePlugin.getDefault(), 
       
   392 					new NullPointerException("Model path is null... perhaps outside workspace?"))); //$NON-NLS-1$
       
   393 		}
       
   394 		IPath fullPath = getFullPath(somePath);
       
   395 		IOwnedModel model;
       
   396 		synchronized (modelLock) {
       
   397 			if (DUMP) System.out.println(providerId+") " + "Get shared " + fullPath); //$NON-NLS-1$ //$NON-NLS-2$
       
   398 			model = releasedModels.get(fullPath);
       
   399 			if (model != null) {
       
   400 				if (DUMP) System.out.println("--> resurrect"); //$NON-NLS-1$
       
   401 				Check.checkState(modelUseCount.get(fullPath).equals(0));
       
   402 				models.put(fullPath, model);
       
   403 				releasedModels.remove(fullPath);
       
   404 				releasedModelTimes.remove(fullPath);
       
   405 			} else {
       
   406 				if (DUMP) System.out.println("--> lookup"); //$NON-NLS-1$
       
   407 				model = models.get(fullPath);
       
   408 			}
       
   409 			if (model == null) {
       
   410 				if (DUMP) System.out.println("--> load"); //$NON-NLS-1$
       
   411 				
       
   412 				// try to load
       
   413 				String text = loadStorage(fullPath);
       
   414 				if (text == null)
       
   415 					return null;
       
   416 				
       
   417 				if (DUMP) System.out.print("--> loading model " + fullPath + "... "); //$NON-NLS-1$ //$NON-NLS-2$
       
   418 				
       
   419 				model = modelFactory.createModel(fullPath, DocumentFactory.createDocument(text));
       
   420 				doRegisterModel(model);
       
   421 				if (DUMP) System.out.println("loaded."); //$NON-NLS-1$
       
   422 			} else {
       
   423 				// check for changes under our nose
       
   424 				if (hasTrackedStorageChanged(fullPath)) {
       
   425 					refreshModel(fullPath, model);
       
   426 				}
       
   427 
       
   428 				// ensure the model is tracked now, if it wasn't before (e.g. because it was not yet in the workspace)
       
   429 				startTrackingModelStorage(model);
       
   430 
       
   431 				incrementUseCount(fullPath);
       
   432 				if (DUMP) System.out.println("--> incr refcount to " + testGetUseCount(model)); //$NON-NLS-1$
       
   433 			}
       
   434 		}
       
   435 		
       
   436 		// try cleaning up (this could be called as a job too)
       
   437 		flushReleasedModels();
       
   438 		
       
   439 		return model;
       
   440 	}
       
   441 
       
   442 	/**
       
   443 	 * TESTING ONLY.
       
   444 	 * <p>
       
   445 	 * @param model
       
   446 	 * @return
       
   447 	 */
       
   448 	public int testGetUseCount(IOwnedModel model) {
       
   449 		return modelUseCount.get(model.getPath());
       
   450 	}
       
   451 	
       
   452 	/**
       
   453 	 * Increment use count for the given model.<p>
       
   454 	 * Called with #modelLock held.
       
   455 	 * @param modelPath
       
   456 	 */
       
   457 	private void incrementUseCount(IPath modelPath) {
       
   458 		Integer val = modelUseCount.get(modelPath);
       
   459 		modelUseCount.put(modelPath, new Integer(val.intValue() + 1));
       
   460 	}
       
   461 
       
   462 	/**
       
   463 	 * Decrement use count for the given model.<p>
       
   464 	 * Called with #modelLock held.
       
   465 	 * @param modelPath
       
   466 	 */
       
   467 	private int decrementUseCount(IPath modelPath) {
       
   468 		Integer val = modelUseCount.get(modelPath);
       
   469 		int newValue = val.intValue() - 1;
       
   470 		Check.checkState(newValue >= 0);
       
   471 		modelUseCount.put(modelPath, new Integer(newValue));
       
   472 		return newValue;
       
   473 	}
       
   474 
       
   475 	/* (non-Javadoc)
       
   476 	 * @see com.nokia.carbide.cpp.epoc.engine.model.IModelProvider#releaseSharedModel(SharedModel)
       
   477 	 */
       
   478 	public void releaseSharedModel(IModel sharedModel) {
       
   479 		IOwnedModel model = (IOwnedModel) sharedModel;
       
   480 		IPath path = model.getPath();
       
   481 		synchronized (modelLock) {
       
   482 			if (DUMP) System.out.println(providerId+") " + "Releasing " + path); //$NON-NLS-1$ //$NON-NLS-2$
       
   483 			Check.checkState(models.containsKey(path));
       
   484 			int val = decrementUseCount(path);
       
   485 			if (val == 0) {
       
   486 				if (sharedModel.getViews().length != 0) {
       
   487 					incrementUseCount(path);
       
   488 					throw new IllegalStateException("Trying to release model with views: " + path); //$NON-NLS-1$
       
   489 				}
       
   490 				if (DUMP) System.out.println("--> refcount == 0"); //$NON-NLS-1$
       
   491 				models.remove(path);
       
   492 				//modelListeners.remove(path);
       
   493 				if (cacheModels) {
       
   494 					if (DUMP) System.out.println("--> released"); //$NON-NLS-1$
       
   495 					// move to released models cache
       
   496 					releasedModels.put(path, model);
       
   497 					releasedModelTimes.put(path, System.currentTimeMillis());
       
   498 				} else {
       
   499 					if (DUMP) System.out.println("--> disposing"); //$NON-NLS-1$
       
   500 					model.setModelProvider(null);
       
   501 					model.dispose();
       
   502 					modelUseCount.remove(path);
       
   503 				}
       
   504 			} else {
       
   505 				if (DUMP) System.out.println("--> refcount == " + val); //$NON-NLS-1$
       
   506 			}
       
   507 		}
       
   508 	}
       
   509 
       
   510 
       
   511 	/**
       
   512 	 * Testing method to clear out the provider's contents. 
       
   513 	 */
       
   514 	public void reset() {
       
   515 		synchronized (modelLock) {
       
   516 			if (DUMP) System.out.println(providerId+") " + "Reset!"); //$NON-NLS-1$ //$NON-NLS-2$
       
   517 			models.clear();
       
   518 			//modelListeners.clear();
       
   519 			modelUseCount.clear();
       
   520 			releasedModelTimes.clear();
       
   521 			releasedModels.clear();
       
   522 		}
       
   523 	}
       
   524 
       
   525 	/**
       
   526 	 * Actually flush a model.  This is done some time after the last
       
   527 	 * #release is called.
       
   528 	 * <p>
       
   529 	 * Called with #modelLock held.
       
   530 	 * @param model
       
   531 	 * @param path
       
   532 	 */
       
   533 	private void flushModel(IPath modelPath) {
       
   534 		if (!releasedModels.containsKey(modelPath)) {
       
   535 			EpocEnginePlugin.log(new IllegalArgumentException("Flushing unreleased model? " + modelPath)); //$NON-NLS-1$
       
   536 		}
       
   537 		try {
       
   538 			IOwnedModel model = releasedModels.get(modelPath);
       
   539 			model.setModelProvider(null);
       
   540 			model.dispose();
       
   541 		} catch (IllegalStateException e) {
       
   542 			throw e;
       
   543 		}
       
   544 		releasedModels.remove(modelPath);
       
   545 		releasedModelTimes.remove(modelPath);
       
   546 		modelUseCount.remove(modelPath);
       
   547 	}
       
   548 
       
   549 	/**
       
   550 	 * Occasionally scan released models and free them,
       
   551 	 * using a delay to avoid overhead of rescanning popular models.
       
   552 	 */
       
   553 	private void flushReleasedModels() {
       
   554 		long now = System.currentTimeMillis();
       
   555 		if (lastFlushTime + RELEASE_DELAY > now) {
       
   556 			return;
       
   557 		}
       
   558 		synchronized (modelLock) {
       
   559 			if (DUMP) System.out.println(providerId+") " + "Flush models... "); //$NON-NLS-1$ //$NON-NLS-2$
       
   560 
       
   561 			// work on copy so #flushModel can fix up everything 
       
   562 			// (else we fix up the time map here, which is not symmetric) 
       
   563 			Map<IPath, Long> timeMap = new HashMap<IPath, Long>(releasedModelTimes);
       
   564 			for (Iterator<Map.Entry<IPath, Long>> iter = timeMap.entrySet().iterator(); iter.hasNext(); ) {
       
   565 				Map.Entry<IPath, Long> entry = iter.next();
       
   566 				if (entry.getValue() + RELEASE_DELAY < now) {
       
   567 					if (DUMP) System.out.println("Flushing model " + entry.getKey()); //$NON-NLS-1$
       
   568 					flushModel(entry.getKey());
       
   569 				}
       
   570 			}
       
   571 			if (DUMP) System.out.println("flushed."); //$NON-NLS-1$
       
   572 		}
       
   573 		lastFlushTime = now;
       
   574 	}
       
   575 
       
   576 	public void cacheModels(boolean flag) {
       
   577 		this.cacheModels = flag;
       
   578 	}
       
   579 	
       
   580 	public boolean isCachingModels() {
       
   581 		return cacheModels;
       
   582 	}
       
   583 	
       
   584 }