blob: fd44da2a87e9de98ad4d52e5c75467f614209814 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.common.snippets.internal.model;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.gef.palette.PaletteContainer;
import org.eclipse.wst.common.snippets.core.ISnippetCategory;
import org.eclipse.wst.common.snippets.core.ISnippetsEntry;
import org.eclipse.wst.common.snippets.internal.IEntryChangeListener;
import org.eclipse.wst.common.snippets.internal.ISnippetManager;
import org.eclipse.wst.common.snippets.internal.Logger;
import org.eclipse.wst.common.snippets.internal.SnippetDefinitions;
import org.eclipse.wst.common.snippets.internal.SnippetsPlugin;
import org.eclipse.wst.common.snippets.internal.palette.ModelFactoryForPlugins;
import org.eclipse.wst.common.snippets.internal.palette.ModelFactoryForUser;
import org.eclipse.wst.common.snippets.internal.palette.SnippetPaletteDrawer;
import org.eclipse.wst.common.snippets.internal.palette.SnippetPaletteItem;
import org.eclipse.wst.common.snippets.internal.palette.SnippetPaletteRoot;
import org.eclipse.wst.common.snippets.internal.palette.UserModelDumper;
import org.eclipse.wst.common.snippets.internal.palette.WorkspaceModelDumper;
import org.eclipse.wst.common.snippets.internal.team.CategoryFileInfo;
import org.eclipse.wst.common.snippets.internal.util.CommonXML;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
*
* SnippetManager
*
* Manages the Library model across multiple views
*/
public class SnippetManager implements ISnippetManager, PropertyChangeListener {
private static final String STORAGE_FOLDER_NAME = "storage"; //$NON-NLS-1$
private static File hiddenStateFile = new File("/hidden.xml"); //$NON-NLS-1$
private static SnippetManager instance = null;
public synchronized static SnippetManager getInstance() {
if (instance == null) {
instance = new SnippetManager();
try {
hiddenStateFile = SnippetsPlugin.getDefault().getStateLocation().append("hidden.xml").toFile(); //$NON-NLS-1$
}
catch (Exception e) {
hiddenStateFile = new File("/hidden.xml"); //$NON-NLS-1$
}
try {
File storageLocation = SnippetsPlugin.getDefault().getStateLocation().append(STORAGE_FOLDER_NAME).toFile();
storageLocation.mkdirs();
}
catch (SecurityException e) {
Logger.logException(e);
}
// hook resource listener and load categories from workspace
// nsd_TODO: disable in-workspace support until fully stabilized
// SnippetsPlugin.getDefault().initializeResourceChangeListener();
}
return instance;
}
private SnippetDefinitions definitions = null;
protected IEntryChangeListener[] fListeners;
private SnippetPaletteRoot fRoot;
public SnippetManager() {
super();
}
public void addEntryChangeListener(IEntryChangeListener listener) {
if (fListeners == null) {
fListeners = new IEntryChangeListener[]{listener};
}
else {
IEntryChangeListener[] newListeners = new IEntryChangeListener[fListeners.length + 1];
newListeners[0] = listener;
System.arraycopy(fListeners, 0, newListeners, 1, fListeners.length);
fListeners = newListeners;
}
}
protected ISnippetsEntry findEntry(SnippetDefinitions defs, String id) {
List categories = defs.getCategories();
ISnippetsEntry match = null;
for (int i = 0; match == null && i < categories.size(); i++) {
SnippetPaletteDrawer category = (SnippetPaletteDrawer) categories.get(i);
if (category.getId().equals(id)) {
match = category;
}
else {
for (int j = 0; match == null && j < category.getChildren().size(); j++) {
SnippetPaletteItem item = (SnippetPaletteItem) category.getChildren().get(j);
if (item.getId().equals(id)) {
match = item;
}
}
}
}
return match;
}
/**
* Find a ISnippetsEntry by it's ID field. Not at all efficient.
*
* @param id
* @return
*/
public ISnippetsEntry findEntry(String id) {
return findEntry(getDefinitions(), id);
}
/**
* @param oldDefinitions
* @param newDefinitions
*/
private void fireModelChanged(SnippetDefinitions oldDefinitions, SnippetDefinitions newDefinitions) {
if (fListeners == null)
return;
for (int i = 0; i < fListeners.length; i++)
fListeners[i].modelChanged(oldDefinitions, newDefinitions);
}
/**
* @return
*/
public SnippetDefinitions getDefinitions() {
if (definitions == null) {
synchronized (getInstance()) {
// load definitions
definitions = loadDefinitions();
if (fRoot == null) {
fRoot = new SnippetPaletteRoot(definitions);
}
// hide categories so marked
String[] hiddenIDs = loadHiddenState();
for (int i = 0; i < hiddenIDs.length; i++) {
ISnippetsEntry entry = findEntry(definitions, hiddenIDs[i]);
if (entry != null) {
if (entry instanceof SnippetPaletteItem) {
((SnippetPaletteItem) entry).setVisible(false);
}
if (entry instanceof SnippetPaletteDrawer) {
((SnippetPaletteDrawer) entry).setVisible(false);
}
}
}
}
}
return definitions;
}
/**
* @return
*/
public IEntryChangeListener[] getListeners() {
return fListeners;
}
public SnippetPaletteRoot getPaletteRoot() {
if (fRoot == null) {
fRoot = new SnippetPaletteRoot(getDefinitions());
}
return fRoot;
}
/**
* @return
*/
protected SnippetDefinitions loadDefinitions() {
// nsd_TODO: Ensure selection is constant across views
// (LibrarySelectionManager?)
SnippetDefinitions defs = new SnippetDefinitions();
// Load all of the definitions from their sources (probably could
// break
// this into extensions with providers)
SnippetDefinitions pluginDefs = ModelFactoryForPlugins.getInstance().loadCurrent();
if (Logger.DEBUG_DEFINITION_PERSISTENCE)
System.out.println("SnippetManager loaded " + pluginDefs.getCategories().size() + " categories from plug-ins"); //$NON-NLS-1$ //$NON-NLS-2$
SnippetDefinitions userDefs = ModelFactoryForUser.getInstance().loadCurrent();
if (Logger.DEBUG_DEFINITION_PERSISTENCE)
System.out.println("SnippetManager loaded " + userDefs.getCategories().size() + " records for categories from user.xml"); //$NON-NLS-1$ //$NON-NLS-2$
// SnippetDefinitions sharedDefs =
// ModelFactoryForWorkspace.getInstance().loadCurrent();
// if (Logger.debugDefinitionPersistence)
// System.out.println("SnippetManager loaded " +
// userDefs.getCategories().size() + " records for categories from
// workspace"); //$NON-NLS-1$ //$NON-NLS-2$
// the user categories also store placeholders for the non-user
// categories
defs.getCategories().addAll(userDefs.getCategories());
// Merge plugin drawers by replacing the placeholder drawers with the
// ones generated from the plugins
for (int i = 0; i < pluginDefs.getCategories().size(); i++) {
SnippetPaletteDrawer category = (SnippetPaletteDrawer) pluginDefs.getCategories().get(i);
String id = category.getId();
ISnippetsEntry existingEntry = findEntry(defs, id);
int existingIndex = defs.getCategories().indexOf(existingEntry);
if (existingEntry != null && existingIndex > -1) {
// copy filters from existingEntry to pluginDefs category
category.setFilters(existingEntry.getFilters());
defs.getCategories().set(existingIndex, category);
}
else {
defs.getCategories().add(category);
}
}
// TODO: Merge workspace drawers
// Remove stale plugin/workspace contributed drawers
// Place-holders are stored without children so any that are not
// refilled
// should be removed. Empty contributed drawers will be removed as
// well.
Iterator it = defs.getCategories().iterator();
while (it.hasNext()) {
SnippetPaletteDrawer category = (SnippetPaletteDrawer) it.next();
if (category.getSourceType() != ISnippetsEntry.SNIPPET_SOURCE_USER && category.getChildren().isEmpty())
it.remove();
}
return defs;
}
/**
* Load the list of categories that aren't to be visible
*/
protected String[] loadHiddenState() {
Document document = null;
String[] results = new String[0];
try {
DocumentBuilder builder = CommonXML.getDocumentBuilder();
if (builder != null) {
document = builder.parse(hiddenStateFile);
}
else {
Logger.log(Logger.ERROR, "SnippetManager couldn't obtain a DocumentBuilder"); //$NON-NLS-1$
}
}
catch (FileNotFoundException e) {
// typical of new workspace, don't log it
document = null;
}
catch (IOException e) {
Logger.logException("SnippetManager could not load hidden state", e); //$NON-NLS-1$
}
catch (SAXException e) {
Logger.logException("SnippetManager could not load hidden state", e); //$NON-NLS-1$
}
if (document != null) {
List names = new ArrayList(0);
Element hidden = document.getDocumentElement();
if (hidden != null) {
NodeList list = hidden.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node childNode = list.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE && childNode.getNodeName().equals(SnippetsPlugin.NAMES.HIDE)) {
Element entry = (Element) childNode;
String id = null;
if (entry.hasAttribute(SnippetsPlugin.NAMES.CATEGORY)) {
id = entry.getAttribute(SnippetsPlugin.NAMES.CATEGORY);
}
else if (entry.hasAttribute(SnippetsPlugin.NAMES.ITEM)) {
id = entry.getAttribute(SnippetsPlugin.NAMES.ITEM);
}
if (id != null && id.length() > 0) {
names.add(id);
}
}
}
results = (String[]) names.toArray(results);
}
}
if (Logger.DEBUG_DEFINITION_PERSISTENCE)
System.out.println("SnippetManager remembered " + results.length + " hidden categories"); //$NON-NLS-1$ //$NON-NLS-2$
return results;
}
/**
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(PaletteContainer.PROPERTY_CHILDREN)) {
SnippetDefinitions oldDefinitions = getDefinitions();
definitions = new SnippetDefinitions();
definitions.setCategories((List) evt.getNewValue());
fireModelChanged(oldDefinitions, definitions);
}
}
public void removeEntryChangeListener(IEntryChangeListener listener) {
if (fListeners.length == 1) {
fListeners = null;
}
else {
List newListenersList = new ArrayList(Arrays.asList(fListeners));
newListenersList.remove(listener);
IEntryChangeListener[] newListeners = new IEntryChangeListener[newListenersList.size() - 1];
newListeners = (IEntryChangeListener[]) newListenersList.toArray(newListeners);
fListeners = newListeners;
}
}
public void resetDefinitions() {
SnippetDefinitions oldDefinitions = definitions;
definitions = null;
if (fRoot != null)
fRoot.setDefinitions(getDefinitions());
fireModelChanged(oldDefinitions, getDefinitions());
}
public synchronized void saveDefinitions() {
if (definitions == null)
return;
// save the list of categories to not see
saveHiddenState();
if (Logger.DEBUG_DEFINITION_PERSISTENCE)
System.out.println("SnippetManager saved hidden state"); //$NON-NLS-1$
// save the model
UserModelDumper.getInstance().write(getDefinitions());
if (Logger.DEBUG_DEFINITION_PERSISTENCE)
System.out.println("SnippetManager wrote user records"); //$NON-NLS-1$
List categories = getDefinitions().getCategories();
final List workspaceCategories = new ArrayList();
for (int i = 0; i < categories.size(); i++) {
ISnippetCategory category = (ISnippetCategory) categories.get(i);
if (category.getSourceType() == ISnippetsEntry.SNIPPET_SOURCE_WORKSPACE) {
CategoryFileInfo info = (CategoryFileInfo) category.getSourceDescriptor();
if (info != null && info.getFile() != null) {
workspaceCategories.add(info);
}
}
}
for (int i = 0; i < workspaceCategories.size(); i++) {
CategoryFileInfo info = (CategoryFileInfo) workspaceCategories.get(i);
try {
if (Logger.DEBUG_DEFINITION_PERSISTENCE)
System.out.println("save workspace category: " + info.getCategory().getLabel()); //$NON-NLS-1$
WorkspaceModelDumper.getInstance().write(info.getCategory(), info.getFile());
}
catch (Exception e) {
Logger.logException(e);
}
}
}
/**
* Save the list of categories that aren't visible
*/
protected void saveHiddenState() {
Document document = CommonXML.getDocumentBuilder().getDOMImplementation().createDocument(null, "hidden", null); //$NON-NLS-1$
try {
FileOutputStream ostream = new FileOutputStream(hiddenStateFile);
List categoryIDs = new ArrayList(0);
List itemIDs = new ArrayList(0);
// collect all of the hidden entry IDs
for (int i = 0; i < getDefinitions().getCategories().size(); i++) {
SnippetPaletteDrawer category = (SnippetPaletteDrawer) getDefinitions().getCategories().get(i);
if (!category.isVisible() && !categoryIDs.contains(category.getId())) {
categoryIDs.add(category.getId());
}
for (int j = 0; j < category.getChildren().size(); j++) {
SnippetPaletteItem entry = (SnippetPaletteItem) category.getChildren().get(j);
if (!entry.isVisible() && !itemIDs.contains(entry.getId())) {
itemIDs.add(entry.getId());
}
}
}
// save those IDs in <hide category="id"/> tags
for (int j = 0; j < categoryIDs.size(); j++) {
Element hidden = document.createElement(SnippetsPlugin.NAMES.HIDE);
hidden.setAttribute(SnippetsPlugin.NAMES.CATEGORY, categoryIDs.get(j).toString());
document.getDocumentElement().appendChild(hidden);
}
for (int j = 0; j < itemIDs.size(); j++) {
Element hidden = document.createElement(SnippetsPlugin.NAMES.HIDE);
hidden.setAttribute(SnippetsPlugin.NAMES.ITEM, itemIDs.get(j).toString());
document.getDocumentElement().appendChild(hidden);
}
CommonXML.serialize(document, ostream);
ostream.close();
}
catch (IOException e) {
Logger.logException(e);
}
}
/**
* @param id
* @return the computed storage location for a snippet with this ID
*/
public IPath getStorageLocation(String id) {
Bundle bundle = Platform.getBundle(SnippetsPlugin.BUNDLE_ID);
return Platform.getStateLocation(bundle).append(STORAGE_FOLDER_NAME).addTrailingSeparator().append(id);
}
/**
* Helper method for saving resources in designated snippet resource folder.
*
* @param snippetId - UID of the snippet that is saving content.
* @param pathList - Project relative path list. The resources are copied in the same structure
* in the storage path. e.g. having test.jsp file in project root will result in
* test.jsp in the storage root. If the jsp is in [Project Root]/resources/test.jsp
* this will result in [Snippet Storage Root]/resources/test.jsp
* @return
*/
public IStatus moveFilesInStorageFolder(IPath snippetStorage, IPath[] pathList, IProject project){
File storageFolder = new File(snippetStorage.toOSString());
if (!storageFolder.exists()){
if (!storageFolder.mkdirs()){
return createErrorStatus("Could not create snippet storage folder.", null); //$NON-NLS-1$
}
}
for (int i = 0; i < pathList.length; i++) {
IFile file = project.getFile(pathList[i]);
IPath destPath = snippetStorage.append(pathList[i]);
File destination = new File(destPath.toOSString());
if (!destination.getParentFile().exists()){
if (!destination.getParentFile().mkdirs()){
return createErrorStatus("Could not create snippet storage folder.", null); //$NON-NLS-1$
}
}
try {
copy(file, destination);
} catch (FileNotFoundException e) {
return createErrorStatus(e.getLocalizedMessage(), e);
} catch (IOException e) {
return createErrorStatus(e.getLocalizedMessage(), e);
} catch (CoreException e) {
return e.getStatus();
}
}
return Status.OK_STATUS;
}
public IStatus moveFilesInWorkspace(File[] fileList, IProject target, IPath storagePath) {
for (int i = 0; i < fileList.length; i++) {
String fileRelativePath = fileList[i].getAbsolutePath().substring(storagePath.toOSString().length());
IFile ifile = target.getFile(new Path(fileRelativePath));
try {
ifile.create(new FileInputStream(fileList[i]), true, new NullProgressMonitor());
} catch (FileNotFoundException e) {
return createErrorStatus(e.getLocalizedMessage(), e);
} catch (CoreException e) {
return createErrorStatus(e.getLocalizedMessage(), e);
}
}
return Status.OK_STATUS;
}
private void copy(IFile source, File destination) throws FileNotFoundException, IOException, CoreException{
copy(source.getContents(), new FileOutputStream(destination));
}
private void copy(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[4096];
try {
int n = in.read(buffer);
while (n > 0) {
out.write(buffer, 0, n);
n = in.read(buffer);
}
} finally {
in.close();
out.close();
}
}
private IStatus createErrorStatus(String msg, Exception e) {
return new Status(IStatus.ERROR, SnippetsPlugin.BUNDLE_ID, msg, e);
}
}