/******************************************************************************* * Copyright (c) 2006 Sybase, Inc. and others. * * 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: * Sybase, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.jst.pagedesigner.editors.palette.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.runtime.content.IContentTypeManager; import org.eclipse.gef.palette.PaletteDrawer; import org.eclipse.gef.palette.PaletteEntry; import org.eclipse.jst.jsf.common.internal.JSPUtil; import org.eclipse.jst.jsf.common.runtime.internal.view.model.common.Namespace; import org.eclipse.jst.jsf.core.internal.CompositeTagRegistryFactory; import org.eclipse.jst.jsf.core.internal.CompositeTagRegistryFactory.TagRegistryIdentifier; import org.eclipse.jst.jsf.designtime.internal.view.model.ITagRegistry; import org.eclipse.jst.jsf.designtime.internal.view.model.ITagRegistry.ITagRegistryListener; import org.eclipse.jst.jsf.designtime.internal.view.model.ITagRegistry.TagRegistryChangeEvent; import org.eclipse.jst.jsf.designtime.internal.view.model.ITagRegistry.TagRegistryChangeEvent.EventType; import org.eclipse.jst.pagedesigner.PDPlugin; import org.eclipse.jst.pagedesigner.editors.palette.DesignerPaletteCustomizationsHelper; import org.eclipse.jst.pagedesigner.editors.palette.IEntryChangeListener; import org.eclipse.jst.pagedesigner.editors.palette.IPaletteConstants; import org.eclipse.jst.pagedesigner.editors.palette.IPaletteContext; import org.eclipse.jst.pagedesigner.editors.palette.IPaletteItemManager; import org.eclipse.wst.html.core.internal.contentmodel.HTMLCMDocumentFactory; import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument; import org.eclipse.wst.xml.core.internal.provisional.contentmodel.CMDocType; /** * Manages tag library palette by palette context. Capable of handling JSP and XHTML content types. * * Callers must use getInstance(IPaletteContext), and when done, call release(IFile). * * @author mengbo and others */ public class PaletteItemManager implements IPaletteItemManager, IPaletteConstants, ITagRegistryListener { private static final boolean DEBUG = false; private static Map<TagRegistryIdentifier, PaletteItemManager> _managers = new HashMap<TagRegistryIdentifier, PaletteItemManager>(); private static ReentrantLock MANAGER_LOCK = new ReentrantLock(); private static long MANAGER_LOCK_TIMEOUT = 120; private Set<IFile> _files = new HashSet<IFile>(); private TagRegistryIdentifier _tagRegId; private List<PaletteDrawer> _paletteCategories = new ArrayList<PaletteDrawer>(); private CopyOnWriteArrayList<IEntryChangeListener> _listeners = new CopyOnWriteArrayList<IEntryChangeListener>(); private AtomicBoolean IS_DISPOSED = new AtomicBoolean(); private PaletteHelper _paletteHelper; private ITagRegistry _tagRegistry; /** * Return singleton paletteItemManager for a given project. Will only work for JSPs. * @param project * @return PaletteItemManager * @deprecated - use getInstance(paletteContext) */ public static PaletteItemManager getInstance(final IProject project) { if (project == null) { // sometimes when the editor is editing a file in jar file, may not // be able to // get the project. return getInstance(createPaletteContext(null)); } //relies on JSP file extension for content type return getInstance(createPaletteContext(project.getFile("dummy.jsp"))); //$NON-NLS-1$ } /** * @param paletteContext * @return PaletteItemManager instance shared with all files with same palette context in a project * May return null if locking issue */ public static PaletteItemManager getInstance(final IPaletteContext paletteContext) { boolean hasLock = false; try { if (MANAGER_LOCK.tryLock(MANAGER_LOCK_TIMEOUT, TimeUnit.SECONDS)){ hasLock = true; final TagRegistryIdentifier regId = getTagRegistryIdentifier(paletteContext); if (regId == null) { PDPlugin.log(new Status(IStatus.ERROR, PDPlugin.getPluginId(), "Unable to display palette for "+paletteContext.getFile().getName()+". Unknown content type for file.")); //$NON-NLS-1$//$NON-NLS-2$ return null; } PaletteItemManager manager = _managers.get(regId); if (manager == null) { manager = new PaletteItemManager(regId); _managers.put(regId, manager); manager.init(); } manager.addFile(paletteContext.getFile()); return manager; } //if we get here then the lock has timed out PDPlugin.log(new Status(Status.ERROR, PDPlugin.getPluginId(), "(getInstance()) Failed to get managers lock for" + paletteContext.getFile().toString())); //$NON-NLS-1$ } catch (InterruptedException e) { PDPlugin.log("Failed in PaletteItemManager.getInstance(PaletteContext", e); //$NON-NLS-1$ } finally { if (hasLock) MANAGER_LOCK.unlock(); } return null; } private static TagRegistryIdentifier getTagRegistryIdentifier( final IPaletteContext paletteContext) { final IFile file = paletteContext.getFile(); if (file != null) { final IContentTypeManager typeManager = Platform.getContentTypeManager(); final IContentType contentType = typeManager.findContentTypeFor(file.getName()); if (contentType != null) { return new TagRegistryIdentifier(file.getProject(), contentType); } return null; } //to support legacy null projects. Allows HTML and JSP tag libs to be displayed. return new TagRegistryIdentifier(null, org.eclipse.jst.pagedesigner.utils.JSPUtil.JSP_CONTENTTYPE); } /** * @param file * @return IPaletteContext */ public static IPaletteContext createPaletteContext(final IFile file) { return new IPaletteContext() { public IFile getFile() { return file; } public Object getAdapter(Class adapter) { return null; } }; } private void addFile(final IFile file) { synchronized (_files) { _files.add(file); } } /** * Indicates that the file no longer needs the paletteItemManager, freeing the manager to be released after last reference * @param paletteContext */ public void release(final IPaletteContext paletteContext) { final IFile file = paletteContext.getFile(); boolean isEmpty = false; synchronized (_files) { if (_files.contains(file)) { _files.remove(file); if (_files.isEmpty()) isEmpty = true; } } if (isEmpty && IS_DISPOSED.compareAndSet(false, true)) { removeTagRegistryListeners(this); boolean hasLock = false; try { if (MANAGER_LOCK.tryLock(MANAGER_LOCK_TIMEOUT, TimeUnit.SECONDS)) { hasLock = true; _managers.remove(_tagRegId); } else { PDPlugin.log(new Status(Status.ERROR, PDPlugin.getPluginId(), "(Release) Failed to get managers lock for" + paletteContext.getFile().toString())); //$NON-NLS-1$ } } catch (InterruptedException e) { PDPlugin.log("Failed to release paletteItemManager for" + paletteContext.getFile().toString(), e); //$NON-NLS-1$ } finally { if (hasLock) MANAGER_LOCK.unlock(); } } } private static void removeTagRegistryListeners(final PaletteItemManager manager) { if (manager.getTagRegistry() != null) manager.getTagRegistry().removeListener(manager); } private ITagRegistry getTagRegistry() { return _tagRegistry; } /** * For JUnit testing purposes only */ public static void clearPaletteItemManager() { boolean hasLock = false; try { if (MANAGER_LOCK.tryLock(MANAGER_LOCK_TIMEOUT, TimeUnit.SECONDS)){ hasLock = true; if (_managers == null) return; for (final PaletteItemManager manager : _managers.values()) { PaletteItemManager.removeTagRegistryListeners(manager); manager._files.clear(); } _managers.clear(); } else { //if we get here then the lock has timed out PDPlugin.log(new Status(Status.ERROR, PDPlugin.getPluginId(), "(clear) Failed to get managers lock")); //$NON-NLS-1$ } } catch (InterruptedException e) { PDPlugin.log("Failed in clearPaletteItemManager", e); //$NON-NLS-1$ } finally { if (hasLock) MANAGER_LOCK.unlock(); } } private PaletteItemManager(final TagRegistryIdentifier regId) { _paletteHelper = new PaletteHelper(this); if (regId != null) { _tagRegId = regId; // init(); } } public List getAllCategories() { synchronized (_paletteCategories) { final List<PaletteDrawer> readOnlyCategories = new ArrayList<PaletteDrawer>(_paletteCategories); return Collections.unmodifiableList(readOnlyCategories); } } /** * Initializes the palette items for the current project */ protected synchronized void init() { synchronized (_paletteCategories) { _paletteCategories.clear(); } initTagRegistry(); DesignerPaletteCustomizationsHelper.loadUserCustomizations(this); sortCategories(); } /** * Sort palette categories */ protected void sortCategories() { //note that once we store ordering customizations, we will need to do something different synchronized(_paletteCategories) { Collections.sort(_paletteCategories, new Comparator(){ public int compare(Object o1, Object o2) { String label1 = ((PaletteEntry)o1).getLabel(); String label2 = ((PaletteEntry)o2).getLabel(); return label1.compareTo(label2); } }); } } /** * Reinitializes the palatteItemManager and informs all palette roots that use the manager to refresh */ public void reset() { init(); fireModelChanged(null, null); } private void initTagRegistry() { registerHTMLCategory(); if (isJSP(_tagRegId)) registerJSPCategory(); registerTagsFromTagRegistry(); } private boolean isJSP(final TagRegistryIdentifier tagRegistryId) { final IContentType ct = tagRegistryId.getContentType(); if (JSPUtil.isJSPContentType(ct.getId())) return true; return false; } private void registerTagsFromTagRegistry() { _tagRegistry = getTagRegistry(_tagRegId); if (_tagRegistry != null) { for (final Namespace ns : _tagRegistry.getAllTagLibraries()) { _paletteHelper.configPaletteItemsByNamespace(this, ns); } } } private ITagRegistry getTagRegistry(final TagRegistryIdentifier regId) { ITagRegistry reg = null; if (regId.getProject() != null) { reg = CompositeTagRegistryFactory.getInstance().getRegistry(regId); if (reg != null) { reg.addListener(this); } } return reg; } private void registerHTMLCategory() { final CMDocument doc = HTMLCMDocumentFactory.getCMDocument(CMDocType.HTML_DOC_TYPE); _paletteHelper.getOrCreateTaglibPaletteDrawer(this, doc, CMDocType.HTML_DOC_TYPE); } private void registerJSPCategory() { final CMDocument doc = HTMLCMDocumentFactory.getCMDocument(CMDocType.JSP11_DOC_TYPE); _paletteHelper.getOrCreateTaglibPaletteDrawer(this, doc, CMDocType.JSP11_DOC_TYPE); } // /** // * Search Classpath entry list to find if the entry is jar library and the // * library have the tld descriptor, if have ,build a palette category mapping // * the tld descriptor. // * // * @param project // */ // private void registerTldFromClasspath(final IProject project) { // if (project != null) { // ITaglibRecord[] tldrecs = TaglibIndex.getAvailableTaglibRecords(project.getFullPath()); // for (int i=0;i<tldrecs.length;i++){ // _paletteHelper.configPaletteItemsByTLD(this, getCurProject(), tldrecs[i]); // } // } // } /** * @param id (most likely the uri) * @param label * @return TaglibPaletteDrawer */ public TaglibPaletteDrawer findOrCreateCategory(final String id, final String label) { TaglibPaletteDrawer category = getTaglibPalletteDrawer(id); if (category == null) category = createTaglibPaletteDrawer(id, label); return category; } /** * @param uri * @return TaglibPaletteDrawer */ public TaglibPaletteDrawer findCategoryByURI(final String uri) { TaglibPaletteDrawer category; for (final Iterator iter = getAllCategories().iterator(); iter.hasNext();) { category = (TaglibPaletteDrawer) iter.next(); if (uri.equals(category.getURI())) { return category; } } return null; } public TaglibPaletteDrawer createTaglibPaletteDrawer(final String uri, final String label) { final TaglibPaletteDrawer r = new TaglibPaletteDrawer(uri, label); synchronized(_paletteCategories) { _paletteCategories.add(r); } return r; } public TaglibPaletteDrawer getTaglibPalletteDrawer(final String uri) { for (final Iterator iter = getAllCategories().iterator(); iter.hasNext();) { final TaglibPaletteDrawer cat = (TaglibPaletteDrawer) iter.next(); if (uri.equalsIgnoreCase(cat.getId())) { return cat; } } return null; } /* * (non-Javadoc) * * @see com.sybase.stf.jmt.pagedesigner.editors.palette.IPaletteItemManager#addEntryChangeListener(com.sybase.stf.jmt.pagedesigner.editors.palette.IEntryChangeListener) */ public void addEntryChangeListener(final IEntryChangeListener listener) { _listeners.addIfAbsent(listener); } /* * (non-Javadoc) * * @see com.sybase.stf.jmt.pagedesigner.editors.palette.IPaletteItemManager#removeEntryChangeListener(com.sybase.stf.jmt.pagedesigner.editors.palette.IEntryChangeListener) */ public void removeEntryChangeListener(final IEntryChangeListener listener) { _listeners.remove(listener); } /** * Notify model change event * * @param oldDefinitions * @param newDefinitions */ private void fireModelChanged(final List oldDefinitions, final List newDefinitions) { if (_listeners == null) { return; } for (final Iterator<IEntryChangeListener> it= _listeners.iterator();it.hasNext();){ final IEntryChangeListener listener = it.next(); listener.modelChanged(oldDefinitions, newDefinitions); } } /** * Informs all paletteItemManagers, except the notifying paletteManager, of updates to the customizations * All palette viewer roots will be notifed of possible updates * @param notifyingManager */ public static void notifyPaletteItemManagersOfCustomizationsUpdate(final IPaletteItemManager notifyingManager){ boolean hasLock = false; try { if (MANAGER_LOCK.tryLock(MANAGER_LOCK_TIMEOUT, TimeUnit.SECONDS)){ hasLock = true; for (Iterator it=_managers.values().iterator();it.hasNext();){ final PaletteItemManager mgr = (PaletteItemManager)it.next(); if (mgr != null && notifyingManager != mgr) mgr.reset(); } } else { //if we get here then the lock has timed out PDPlugin.log(new Status(Status.ERROR, PDPlugin.getPluginId(), "Failed to get managers lock in notifyPaletteItemManagersOfCustomizationsUpdate")); //$NON-NLS-1$ } } catch (InterruptedException e) { PDPlugin.log("Failed in notifyPaletteItemManagersOfCustomizationsUpdate", e); //$NON-NLS-1$ } finally { if (hasLock) MANAGER_LOCK.unlock(); } } public void registryChanged(final TagRegistryChangeEvent event) { final EventType eventType = event.getType(); switch (eventType) { case ADDED_NAMESPACE: addNamespaces(event.getAffectedObjects()); break; case REMOVED_NAMESPACE: removeNamespaces(event.getAffectedObjects()); break; case CHANGED_NAMESPACE: changeNamespaces(event.getAffectedObjects()); break; case REGISTRY_DISPOSED: break; default: break; } DesignerPaletteCustomizationsHelper.loadUserCustomizations(this); sortCategories(); fireModelChanged(null, null); } private void addNamespaces(final List<? extends Namespace> affectedObjects) { synchronized (_paletteCategories) { for (final Namespace ns : affectedObjects) { if (DEBUG) System.out.println("Add NS: "+ns.getNSUri()+"["+System.currentTimeMillis()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ _paletteHelper.configPaletteItemsByNamespace(this, ns); } } } private void removeNamespaces(final List<? extends Namespace> affectedObjects) { final List<Integer> drawersToRemove = new ArrayList<Integer>(); synchronized (_paletteCategories) { for (final Namespace ns : affectedObjects) { for (int i=_paletteCategories.size() - 1; i >= 0; i--) {//gather in reverse order final PaletteDrawer drawer = _paletteCategories.get(i); if (drawer.getId().equals(ns.getNSUri())) { if (DEBUG) System.out.println("Remove NS: "+drawer.getId() +"["+System.currentTimeMillis()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ drawersToRemove.add(new Integer(i)); } } } if (! drawersToRemove.isEmpty()) { Collections.sort(drawersToRemove, new Comparator<Integer>() {//reverse order sort public int compare(Integer o1, Integer o2) { if (o1.intValue() > o2.intValue()) return -1; else if (o1.intValue() < o2.intValue()) return 1; return 0; } }); for (Integer index : drawersToRemove) { _paletteCategories.remove(index.intValue()); } } } } private void changeNamespaces(final List<? extends Namespace> affectedObjects) { //for now, remove then add removeNamespaces(affectedObjects); addNamespaces(affectedObjects); } public TagRegistryIdentifier getTagRegistryIdentifier() { return _tagRegId; } /** * @return helper */ public PaletteHelper getPaletteHelper() { return _paletteHelper; } }