blob: af1ca6a5ce6335f06fc0c00272435dc283b1ac6f [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}