| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * 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.text.templates; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.osgi.service.prefs.BackingStoreException; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; |
| |
| import org.eclipse.text.templates.ContextTypeRegistry; |
| import org.eclipse.text.templates.TemplatePersistenceData; |
| |
| import org.eclipse.jface.text.templates.Template; |
| import org.eclipse.jface.text.templates.TemplateException; |
| |
| /** |
| * A collection of templates. Clients may instantiate this class. In order to |
| * load templates contributed using the <code>org.eclipse.ui.editors.templates</code> |
| * extension point, use a <code>ContributionTemplateStore</code>. |
| * |
| * @since 3.7 |
| */ |
| public class TemplateStoreCore { |
| |
| /** The stored templates. */ |
| private final List<TemplatePersistenceData> fTemplates= new ArrayList<>(); |
| /** The preference store. */ |
| private IEclipsePreferences fPreferenceStore; |
| /** |
| * The key into <code>fPreferenceStore</code> the value of which holds custom templates |
| * encoded as XML. |
| */ |
| private String fKey; |
| /** |
| * The context type registry, or <code>null</code> if all templates regardless |
| * of context type should be loaded. |
| */ |
| private ContextTypeRegistry fRegistry; |
| /** |
| * Set to <code>true</code> if property change events should be ignored (e.g. during writing |
| * to the preference store). |
| * |
| * @since 3.2 |
| */ |
| private boolean fIgnorePreferenceStoreChanges= false; |
| /** |
| * The property listener, if any is registered, <code>null</code> otherwise. |
| * |
| * @since 3.2 |
| */ |
| private IPreferenceChangeListener fPropertyListener; |
| |
| |
| /** |
| * Creates a new template store. |
| * |
| * @param store the preference store in which to store custom templates |
| * under <code>key</code> |
| * @param key the key into <code>store</code> where to store custom |
| * templates |
| */ |
| public TemplateStoreCore(IEclipsePreferences store, String key) { |
| Assert.isNotNull(key); |
| fPreferenceStore= store; |
| fKey= key; |
| } |
| |
| /** |
| * Creates a new template store with a context type registry. Only templates |
| * that specify a context type contained in the registry will be loaded by |
| * this store if the registry is not <code>null</code>. |
| * |
| * @param registry a context type registry, or <code>null</code> if all |
| * templates should be loaded |
| * @param store the preference store in which to store custom templates |
| * under <code>key</code> |
| * @param key the key into <code>store</code> where to store custom |
| * templates |
| */ |
| public TemplateStoreCore(ContextTypeRegistry registry, IEclipsePreferences store, String key) { |
| this(store, key); |
| fRegistry= registry; |
| } |
| |
| /** |
| * Loads the templates from contributions and preferences. |
| * |
| * @throws IOException if loading fails. |
| */ |
| public void load() throws IOException { |
| fTemplates.clear(); |
| loadContributedTemplates(); |
| loadCustomTemplates(); |
| } |
| |
| /** |
| * Starts listening for property changes on the preference store. If the configured preference |
| * key changes, the template store is {@link #load() reloaded}. Call |
| * {@link #stopListeningForPreferenceChanges()} to remove any listener and stop the |
| * auto-updating behavior. |
| * |
| * @since 3.2 |
| */ |
| public void startListeningForPreferenceChanges() { |
| if (fPropertyListener == null) { |
| fPropertyListener= new IPreferenceChangeListener() { |
| @Override |
| public void preferenceChange(PreferenceChangeEvent event) { |
| /* |
| * Don't load if we are in the process of saving ourselves. We are in sync anyway after the |
| * save operation, and clients may trigger reloading by listening to preference store |
| * updates. |
| */ |
| if (!fIgnorePreferenceStoreChanges && fKey.equals(event.getKey())) |
| try { |
| load(); |
| } catch (IOException x) { |
| handleException(x); |
| } |
| } |
| }; |
| fPreferenceStore.addPreferenceChangeListener(fPropertyListener); |
| } |
| |
| } |
| |
| /** |
| * Stops the auto-updating behavior started by calling |
| * {@link #startListeningForPreferenceChanges()}. |
| * |
| * @since 3.2 |
| */ |
| public void stopListeningForPreferenceChanges() { |
| if (fPropertyListener != null) { |
| fPreferenceStore.removePreferenceChangeListener(fPropertyListener); |
| fPropertyListener= null; |
| } |
| } |
| |
| /** |
| * Handles an {@link IOException} thrown during reloading the preferences due to a preference |
| * store update. The default is to write to stderr. |
| * |
| * @param x the exception |
| * @since 3.2 |
| */ |
| protected void handleException(IOException x) { |
| x.printStackTrace(); |
| } |
| |
| /** |
| * Hook method to load contributed templates. Contributed templates are superseded |
| * by customized versions of user added templates stored in the preferences. |
| * <p> |
| * The default implementation does nothing.</p> |
| * |
| * @throws IOException if loading fails |
| */ |
| protected void loadContributedTemplates() throws IOException { |
| } |
| |
| /** |
| * Adds a template to the internal store. The added templates must have |
| * a unique id. |
| * |
| * @param data the template data to add |
| */ |
| protected void internalAdd(TemplatePersistenceData data) { |
| if (!data.isCustom()) { |
| // check if the added template is not a duplicate id |
| String id= data.getId(); |
| for (TemplatePersistenceData persistenceData : fTemplates) { |
| if (persistenceData.getId() != null && persistenceData.getId().equals(id)) |
| return; |
| } |
| fTemplates.add(data); |
| } |
| } |
| |
| /** |
| * Saves the templates to the preferences. |
| * |
| * @throws IOException if the templates cannot be written |
| */ |
| public void save() throws IOException { |
| ArrayList<TemplatePersistenceData> custom= new ArrayList<>(); |
| for (TemplatePersistenceData data : fTemplates) { |
| if (data.isCustom() && !(data.isUserAdded() && data.isDeleted())) // don't save deleted user-added templates |
| custom.add(data); |
| } |
| |
| StringWriter output= new StringWriter(); |
| TemplateReaderWriter writer= new TemplateReaderWriter(); |
| writer.save(custom.toArray(new TemplatePersistenceData[custom.size()]), output); |
| |
| fIgnorePreferenceStoreChanges= true; |
| try { |
| fPreferenceStore.put(fKey, output.toString()); |
| fPreferenceStore.flush(); |
| } catch (BackingStoreException e) { |
| } finally { |
| fIgnorePreferenceStoreChanges= false; |
| } |
| } |
| |
| /** |
| * Adds a template encapsulated in its persistent form. |
| * |
| * @param data the template to add |
| */ |
| public void add(TemplatePersistenceData data) { |
| |
| if (!validateTemplate(data.getTemplate())) |
| return; |
| |
| if (data.isUserAdded()) { |
| fTemplates.add(data); |
| } else { |
| for (TemplatePersistenceData persistenceData : fTemplates) { |
| if (persistenceData.getId() != null && persistenceData.getId().equals(data.getId())) { |
| persistenceData.setTemplate(data.getTemplate()); |
| persistenceData.setDeleted(data.isDeleted()); |
| persistenceData.setEnabled(data.isEnabled()); |
| return; |
| } |
| } |
| |
| // add an id which is not contributed as add-on |
| if (data.getTemplate() != null) { |
| TemplatePersistenceData newData= new TemplatePersistenceData(data.getTemplate(), data.isEnabled(), data.getId()); |
| fTemplates.add(newData); |
| } |
| } |
| } |
| |
| /** |
| * Removes a template from the store. |
| * |
| * @param data the template to remove |
| */ |
| public void delete(TemplatePersistenceData data) { |
| if (data.isUserAdded()) |
| fTemplates.remove(data); |
| else |
| data.setDeleted(true); |
| } |
| |
| /** |
| * Restores all contributed templates that have been deleted. |
| */ |
| public void restoreDeleted() { |
| for (TemplatePersistenceData data : fTemplates) { |
| if (data.isDeleted()) |
| data.setDeleted(false); |
| } |
| } |
| |
| /** |
| * Deletes all user-added templates and reverts all contributed templates. |
| * |
| * @param doSave <code>true</code> if the store should be saved after restoring |
| * @since 3.5 |
| */ |
| public void restoreDefaults(boolean doSave) { |
| String oldValue= null; |
| if (!doSave) |
| oldValue= fPreferenceStore.get(fKey, null); |
| |
| try { |
| fIgnorePreferenceStoreChanges= true; |
| // See IPreferenceStore for default String value |
| fPreferenceStore.put(fKey, ""); //$NON-NLS-1$ |
| } finally { |
| fIgnorePreferenceStoreChanges= false; |
| } |
| |
| try { |
| load(); |
| } catch (IOException x) { |
| // can't log from jface-text |
| handleException(x); |
| } |
| |
| if (oldValue != null) { |
| try { |
| fIgnorePreferenceStoreChanges= true; |
| fPreferenceStore.put(fKey, oldValue); |
| } finally { |
| fIgnorePreferenceStoreChanges= false; |
| } |
| } |
| } |
| |
| /** |
| * Deletes all user-added templates and reverts all contributed templates. |
| * <p> |
| * <strong>Note:</strong> the store will be saved after restoring. |
| * </p> |
| */ |
| public void restoreDefaults() { |
| restoreDefaults(true); |
| } |
| |
| /** |
| * Returns all enabled templates. |
| * |
| * @return all enabled templates |
| */ |
| public Template[] getTemplates() { |
| return getTemplates(null); |
| } |
| |
| /** |
| * Returns all enabled templates for the given context type. |
| * |
| * @param contextTypeId the id of the context type of the requested templates, or <code>null</code> if all templates should be returned |
| * @return all enabled templates for the given context type |
| */ |
| public Template[] getTemplates(String contextTypeId) { |
| List<Template> templates= new ArrayList<>(); |
| for (TemplatePersistenceData data : fTemplates) { |
| if (data.isEnabled() && !data.isDeleted() && (contextTypeId == null || contextTypeId.equals(data.getTemplate().getContextTypeId()))) |
| templates.add(data.getTemplate()); |
| } |
| |
| return templates.toArray(new Template[templates.size()]); |
| } |
| |
| /** |
| * Returns the first enabled template that matches the name. |
| * |
| * @param name the name of the template searched for |
| * @return the first enabled template that matches both name and context type id, or <code>null</code> if none is found |
| */ |
| public Template findTemplate(String name) { |
| return findTemplate(name, null); |
| } |
| |
| /** |
| * Returns the first enabled template that matches both name and context type id. |
| * |
| * @param name the name of the template searched for |
| * @param contextTypeId the context type id to clip unwanted templates, or <code>null</code> if any context type is OK |
| * @return the first enabled template that matches both name and context type id, or <code>null</code> if none is found |
| */ |
| public Template findTemplate(String name, String contextTypeId) { |
| Assert.isNotNull(name); |
| |
| for (TemplatePersistenceData data : fTemplates) { |
| Template template= data.getTemplate(); |
| if (data.isEnabled() && !data.isDeleted() |
| && (contextTypeId == null || contextTypeId.equals(template.getContextTypeId())) |
| && name.equals(template.getName())) |
| return template; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the first enabled template that matches the given template id. |
| * |
| * @param id the id of the template searched for |
| * @return the first enabled template that matches id, or <code>null</code> if none is found |
| * @since 3.1 |
| */ |
| public Template findTemplateById(String id) { |
| TemplatePersistenceData data= getTemplateData(id); |
| if (data != null && !data.isDeleted()) |
| return data.getTemplate(); |
| |
| return null; |
| } |
| |
| /** |
| * Returns all template data. |
| * |
| * @param includeDeleted whether to include deleted data |
| * @return all template data, whether enabled or not |
| */ |
| public TemplatePersistenceData[] getTemplateData(boolean includeDeleted) { |
| List<TemplatePersistenceData> datas= new ArrayList<>(); |
| for (TemplatePersistenceData data : fTemplates) { |
| if (includeDeleted || !data.isDeleted()) |
| datas.add(data); |
| } |
| |
| return datas.toArray(new TemplatePersistenceData[datas.size()]); |
| } |
| |
| /** |
| * Returns the template data of the template with id <code>id</code> or |
| * <code>null</code> if no such template can be found. |
| * |
| * @param id the id of the template data |
| * @return the template data of the template with id <code>id</code> or <code>null</code> |
| * @since 3.1 |
| */ |
| public TemplatePersistenceData getTemplateData(String id) { |
| Assert.isNotNull(id); |
| for (TemplatePersistenceData data : fTemplates) { |
| if (id.equals(data.getId())) |
| return data; |
| } |
| |
| return null; |
| } |
| |
| private void loadCustomTemplates() throws IOException { |
| String pref= fPreferenceStore.get(fKey, null); |
| if (pref != null && pref.trim().length() > 0) { |
| Reader input= new StringReader(pref); |
| TemplateReaderWriter reader= new TemplateReaderWriter(); |
| TemplatePersistenceData[] datas= reader.read(input); |
| for (TemplatePersistenceData data : datas) { |
| add(data); |
| } |
| } |
| } |
| |
| /** |
| * Validates a template against the context type registered in the context |
| * type registry. Returns always <code>true</code> if no registry is |
| * present. |
| * |
| * @param template the template to validate |
| * @return <code>true</code> if validation is successful or no context |
| * type registry is specified, <code>false</code> if validation |
| * fails |
| */ |
| private boolean validateTemplate(Template template) { |
| String contextTypeId= template.getContextTypeId(); |
| if (contextExists(contextTypeId)) { |
| if (fRegistry != null) |
| try { |
| fRegistry.getContextType(contextTypeId).validate(template.getPattern()); |
| } catch (TemplateException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if a context type id specifies a valid context type |
| * or if no context type registry is present. |
| * |
| * @param contextTypeId the context type id to look for |
| * @return <code>true</code> if the context type specified by the id |
| * is present in the context type registry, or if no registry is |
| * specified |
| */ |
| private boolean contextExists(String contextTypeId) { |
| return contextTypeId != null && (fRegistry == null || fRegistry.getContextType(contextTypeId) != null); |
| } |
| |
| /** |
| * Returns the registry. |
| * |
| * @return Returns the registry |
| */ |
| protected ContextTypeRegistry getRegistry() { |
| return fRegistry; |
| } |
| |
| /** |
| * Return the key into the Preference Store whose value contains |
| * the custom templates encoded as XML. |
| * |
| * @return the key in the Preference Store |
| */ |
| protected final String getKey () { |
| return fKey; |
| } |
| |
| /** |
| * Return the stored templates |
| * @return the stored templates |
| */ |
| protected final List<TemplatePersistenceData> internalGetTemplates () { |
| return fTemplates; |
| } |
| |
| } |
| |