/******************************************************************************* * Copyright (c) 2006 Oracle Corporation. * 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: * Cameron Bateman/Oracle - initial API and implementation * ********************************************************************************/ package org.eclipse.jst.jsf.designtime.internal.symbols; import java.io.IOException; import java.io.InputStream; import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IStorage; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jst.jsf.common.internal.resource.EventResult; import org.eclipse.jst.jsf.common.internal.resource.IResourceLifecycleListener; import org.eclipse.jst.jsf.common.internal.resource.LifecycleListener; import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent; import org.eclipse.jst.jsf.common.internal.resource.ResourceLifecycleEvent.EventType; import org.eclipse.jst.jsf.context.symbol.internal.impl.IMapSourceInfo; import org.eclipse.jst.jsf.core.internal.JSFCorePlugin; import org.eclipse.jst.jsf.core.internal.tld.LoadBundleUtil; class ResourceBundleMapSource extends AbstractMap implements IMapSourceInfo { private static final String PROPERTY_QUALIFIER = "org.eclipse.jst.jsf.designtime.internal.jsp"; //$NON-NLS-1$ private static final String SESSION_PROPERTY_NAME_PROJECT = "ResourceBundleMapSource"; //$NON-NLS-1$ private static final QualifiedName SESSION_PROPERTY_KEY_PROJECT = new QualifiedName(PROPERTY_QUALIFIER, SESSION_PROPERTY_NAME_PROJECT); private static final Object STATIC_LOCK = new Object(); private static IFile getCachedBundleFile(final IProject project, final String baseName) { if (project != null) { final Map<String, BundleFileCacheInfo> bundleFileCache = getBundleFileCache(project); if (bundleFileCache != null) { final BundleFileCacheInfo info = bundleFileCache.get(baseName); if (info != null) { return info.getFile(); } } } return null; } private static class BundleFileCacheInfo { private final IFile _file; private final Map<Object,CachedDataItem> _cachedData; public BundleFileCacheInfo(final IFile file) { super(); _file = file; _cachedData = Collections.synchronizedMap(new HashMap<Object, CachedDataItem>()); } public IFile getFile() { return _file; } public Object getCachedData(final Object key) { CachedDataItem item = _cachedData.get(key); if (item != null) { return item.getData(); } return null; } public Object putCachedData(final Object key, final Object value, final long timestamp) { CachedDataItem item = new CachedDataItem(value, timestamp); CachedDataItem oldItem = _cachedData.put(key, item); if (oldItem != null) { return oldItem.getData(); } return null; } public boolean hasChanged(final Object key, final long timestamp) { CachedDataItem item = _cachedData.get(key); if (item != null) { return item.getTimestamp() != timestamp; } return false; } private static class CachedDataItem { private final Object _data; private final long _timestamp; public CachedDataItem(Object data, long timestamp) { super(); _data = data; _timestamp = timestamp; } public final Object getData() { return _data; } public final long getTimestamp() { return _timestamp; } } } private static Map<String, BundleFileCacheInfo> getBundleFileCache(final IProject project) { synchronized(STATIC_LOCK) { Map<String, BundleFileCacheInfo> bundleFileCache = null; try { bundleFileCache = (Map<String, BundleFileCacheInfo>) project.getSessionProperty(SESSION_PROPERTY_KEY_PROJECT); if (bundleFileCache == null) { bundleFileCache = new HashMap<String, BundleFileCacheInfo>(); final LifecycleListener listener = new LifecycleListener(project, ResourcesPlugin.getWorkspace()); listener.addListener(new IResourceLifecycleListener() { public EventResult acceptEvent(final ResourceLifecycleEvent event) { EventResult result = EventResult.getDefaultEventResult(); if (event.getEventType() == EventType.RESOURCE_INACCESSIBLE) { try { //Bug 283764: this listener may be called more than once - check project not already inaccessible if (project.isAccessible()) { final Map<String, BundleFileCacheInfo> bundleCache = (Map<String, BundleFileCacheInfo>) project.getSessionProperty(SESSION_PROPERTY_KEY_PROJECT); //This listener may be called more than once - check session property not already nulled if (bundleCache != null) { bundleCache.clear(); project.setSessionProperty(SESSION_PROPERTY_KEY_PROJECT, null); } } } catch (final CoreException ce) { JSFCorePlugin.log("Error clearing bundle file cache", ce); //$NON-NLS-1$ } result = EventResult.getDisposeAfterEventResult(); } return result; } } ); project.setSessionProperty(SESSION_PROPERTY_KEY_PROJECT, bundleFileCache); } } catch (final CoreException ce) { JSFCorePlugin.log("Error creating bundle file cache", ce); //$NON-NLS-1$ } return bundleFileCache; } } private static IFile createCachedBundleFile(final IProject project, final String resourcePathStr) throws IOException, CoreException { final IStorage storage = LoadBundleUtil.getLoadBundleResource(project, resourcePathStr); IFile bundleRes = null; if (storage != null && storage.getAdapter(IFile.class) != null) { bundleRes = (IFile) storage.getAdapter(IFile.class); // if file is removed, clear the bundle from the store. final LifecycleListener listener = new LifecycleListener(bundleRes, ResourcesPlugin.getWorkspace()); listener.addListener(new IResourceLifecycleListener() { public EventResult acceptEvent(final ResourceLifecycleEvent event) { EventResult result = EventResult.getDefaultEventResult(); if (event.getEventType() == EventType.RESOURCE_INACCESSIBLE) { Map<String, BundleFileCacheInfo> bundleFileCache = getBundleFileCache(project); bundleFileCache.remove(resourcePathStr); result = EventResult.getDisposeAfterEventResult(); } return result; } } ); getBundleFileCache(project).put(resourcePathStr, new BundleFileCacheInfo(bundleRes)); return bundleRes; } throw new IOException("Bundle "+resourcePathStr+" not found in classpath for project: "+project.getName()); //$NON-NLS-1$ //$NON-NLS-2$ } private Properties _resourceBundle; // = null; set on first access or changes private final IFile _bundleFile; // the resource private final String _resourcePathStr; // the key used in the file cache // as returned by IResource.getModificationStamp() // the last time _resourceBundle was loaded private long _lastModificationStamp; ResourceBundleMapSource(final IProject context, final String resourcePathStr) throws IOException, JavaModelException, CoreException { IFile cachedBundleFile = getCachedBundleFile(context, resourcePathStr); if (cachedBundleFile == null) { cachedBundleFile = createCachedBundleFile(context, resourcePathStr); } _bundleFile = cachedBundleFile; _resourcePathStr = resourcePathStr; } private void checkAndRefreshBundle() { if (_bundleFile.isAccessible()) { if (_resourceBundle == null // doesn't exist yet // exists but ws is out of sync || !_bundleFile.isSynchronized(IResource.DEPTH_ZERO) // exists but user has changed in workspace || _bundleFile.getModificationStamp() != _lastModificationStamp) { InputStream bundleStream = null; try { // force refresh if out of sync bundleStream = _bundleFile.getContents(true); _resourceBundle = new Properties(); _resourceBundle.load(bundleStream); _lastModificationStamp = _bundleFile.getModificationStamp(); } catch (final CoreException ce) { JSFCorePlugin.log("Error refreshing bundle", ce); //$NON-NLS-1$ } catch (final IOException ioe) { JSFCorePlugin.log("Error refreshing bundle", ioe); //$NON-NLS-1$ } finally { if (bundleStream != null) { try { bundleStream.close(); } catch (final IOException ioe) { JSFCorePlugin.log("Error closing bundle", ioe); //$NON-NLS-1$ } } } } } else { // bundle no longer exists so remove it final Map<String, BundleFileCacheInfo> bundleFileCache = getBundleFileCache(_bundleFile.getProject()); if (bundleFileCache != null && bundleFileCache.containsKey(_resourcePathStr)) { bundleFileCache.remove(_resourcePathStr); } // in either case, clear the bundle entry if (_resourceBundle != null) { _resourceBundle.clear(); _resourceBundle = null; } } } @Override public Set entrySet() { checkAndRefreshBundle(); if (_resourceBundle == null) { return Collections.EMPTY_SET; } return _resourceBundle.entrySet(); } /** * @param key * @return the value * @see java.util.AbstractMap#get(java.lang.Object) * @overrride to optimize for the fact that we are doing a hash get */ // @Override public Object get(final Object key) { checkAndRefreshBundle(); if (_resourceBundle == null) { return null; } return _resourceBundle.get(key); } public final boolean hasChanged(final Object key) { final BundleFileCacheInfo cache = getBundleFileCache( _bundleFile.getProject()).get(_resourcePathStr); if (cache != null) { return cache.hasChanged(key, _bundleFile.getModificationStamp()); } // return true since if there is nothing cached, the caller will want // to react. return true; } public Object getCachedValue(final Object key) { final BundleFileCacheInfo cache = getBundleFileCache( _bundleFile.getProject()).get(_resourcePathStr); if (cache != null) { return cache.getCachedData(key); } return null; } public void putCachedValue(final Object key, final Object value) { final BundleFileCacheInfo cache = getBundleFileCache( _bundleFile.getProject()).get(_resourcePathStr); if (cache != null) { cache.putCachedData(key, value, _lastModificationStamp); } } }