diff options
Diffstat (limited to 'org.eclipse.text/src/org/eclipse/text/templates')
6 files changed, 1219 insertions, 0 deletions
diff --git a/org.eclipse.text/src/org/eclipse/text/templates/ContextTypeRegistry.java b/org.eclipse.text/src/org/eclipse/text/templates/ContextTypeRegistry.java new file mode 100644 index 00000000000..a966cae545e --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/text/templates/ContextTypeRegistry.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2000, 2018 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.templates; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jface.text.templates.TemplateContextType; + +/** + * A registry for context types. Editor implementors will usually instantiate a + * registry and configure the context types available in their editor. + * <p> + * In order to pick up templates contributed using the <code>org.eclipse.ui.editors.templates</code> + * extension point, use a <code>ContributionContextTypeRegistry</code>. + * </p> + * + * @since 3.7 + */ +public class ContextTypeRegistry { + + /** all known context types */ + private final Map<String, TemplateContextType> fContextTypes= new LinkedHashMap<>(); + + /** + * Adds a context type to the registry. If there already is a context type + * with the same ID registered, it is replaced. + * + * @param contextType the context type to add + */ + public void addContextType(TemplateContextType contextType) { + fContextTypes.put(contextType.getId(), contextType); + } + + /** + * Returns the context type if the id is valid, <code>null</code> otherwise. + * + * @param id the id of the context type to retrieve + * @return the context type if <code>name</code> is valid, <code>null</code> otherwise + */ + public TemplateContextType getContextType(String id) { + return fContextTypes.get(id); + } + + /** + * Returns an iterator over all registered context types. + * + * @return an iterator over all registered context types + */ + public Iterator<TemplateContextType> contextTypes() { + return fContextTypes.values().iterator(); + } +} diff --git a/org.eclipse.text/src/org/eclipse/text/templates/TemplatePersistenceData.java b/org.eclipse.text/src/org/eclipse/text/templates/TemplatePersistenceData.java new file mode 100644 index 00000000000..60dfc06f2a0 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/text/templates/TemplatePersistenceData.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2000, 2018 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.templates; + +import org.eclipse.core.runtime.Assert; + +import org.eclipse.jface.text.templates.Template; + + +/** + * TemplatePersistenceData stores information about a template. It uniquely + * references contributed templates via their id. Contributed templates may be + * deleted or modified. All template may be enabled or not. + * <p> + * Clients may use this class, although this is not usually needed except when + * implementing a custom template preference page or template store. This class + * is not intended to be subclassed. + * </p> + * + * @since 3.7 + * @noextend This class is not intended to be subclassed by clients. + */ +public class TemplatePersistenceData { + private final Template fOriginalTemplate; + private final String fId; + private final boolean fOriginalIsEnabled; + + private Template fCustomTemplate= null; + private boolean fIsDeleted= false; + private boolean fCustomIsEnabled= true; + + /** + * Creates a new, user-added instance that is not linked to a contributed + * template. + * + * @param template the template which is stored by the new instance + * @param enabled whether the template is enabled + */ + public TemplatePersistenceData(Template template, boolean enabled) { + this(template, enabled, null); + } + + /** + * Creates a new instance. If <code>id</code> is not <code>null</code>, + * the instance is represents a template that is contributed and can be + * identified via its id. + * + * @param template the template which is stored by the new instance + * @param enabled whether the template is enabled + * @param id the id of the template, or <code>null</code> if a user-added + * instance should be created + */ + public TemplatePersistenceData(Template template, boolean enabled, String id) { + Assert.isNotNull(template); + fOriginalTemplate= template; + fCustomTemplate= template; + fOriginalIsEnabled= enabled; + fCustomIsEnabled= enabled; + fId= id; + } + + /** + * Returns the id of this template store, or <code>null</code> if there is none. + * + * @return the id of this template store + */ + public String getId() { + return fId; + } + + /** + * Returns the deletion state of the stored template. This is only relevant + * of contributed templates. + * + * @return the deletion state of the stored template + */ + public boolean isDeleted() { + return fIsDeleted; + } + + /** + * Sets the deletion state of the stored template. + * + * @param isDeleted the deletion state of the stored template + */ + public void setDeleted(boolean isDeleted) { + fIsDeleted= isDeleted; + } + + /** + * Returns the template encapsulated by the receiver. + * + * @return the template encapsulated by the receiver + */ + public Template getTemplate() { + return fCustomTemplate; + } + + + /** + * Sets the template encapsulated by the receiver. + * + * @param template the new template + */ + public void setTemplate(Template template) { + fCustomTemplate= template; + } + + /** + * Returns whether the receiver represents a custom template, i.e. is either + * a user-added template or a contributed template that has been modified. + * + * @return <code>true</code> if the contained template is a custom + * template and cannot be reconstructed from the contributed + * templates + */ + public boolean isCustom() { + return fId == null + || fIsDeleted + || fOriginalIsEnabled != fCustomIsEnabled + || !fOriginalTemplate.equals(fCustomTemplate); + } + + /** + * Returns whether the receiver represents a modified template, i.e. a + * contributed template that has been changed. + * + * @return <code>true</code> if the contained template is contributed but has been modified, <code>false</code> otherwise + */ + public boolean isModified() { + return isCustom() && !isUserAdded(); + } + + /** + * Returns <code>true</code> if the contained template was added by a + * user, i.e. does not reference a contributed template. + * + * @return <code>true</code> if the contained template was added by a user, <code>false</code> otherwise + */ + public boolean isUserAdded() { + return fId == null; + } + + + /** + * Reverts the template to its original setting. + */ + public void revert() { + fCustomTemplate= fOriginalTemplate; + fCustomIsEnabled= fOriginalIsEnabled; + fIsDeleted= false; + } + + + /** + * Returns the enablement state of the contained template. + * + * @return the enablement state of the contained template + */ + public boolean isEnabled() { + return fCustomIsEnabled; + } + + /** + * Sets the enablement state of the contained template. + * + * @param isEnabled the new enablement state of the contained template + */ + public void setEnabled(boolean isEnabled) { + fCustomIsEnabled= isEnabled; + } +} diff --git a/org.eclipse.text/src/org/eclipse/text/templates/TemplateReaderWriter.java b/org.eclipse.text/src/org/eclipse/text/templates/TemplateReaderWriter.java new file mode 100644 index 00000000000..3abeec0dbc2 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/text/templates/TemplateReaderWriter.java @@ -0,0 +1,414 @@ +/******************************************************************************* + * Copyright (c) 2000, 2018 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.templates; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import org.eclipse.osgi.util.NLS; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; + +import org.eclipse.jface.text.templates.Template; + +/** + * Serializes templates as character or byte stream and reads the same format + * back. + * <p> + * Clients may instantiate this class, it is not intended to be + * subclassed.</p> + * + * @since 3.7 + * @noextend This class is not intended to be subclassed by clients. + */ +public class TemplateReaderWriter { + + private static final String TEMPLATE_ROOT = "templates"; //$NON-NLS-1$ + private static final String TEMPLATE_ELEMENT = "template"; //$NON-NLS-1$ + private static final String NAME_ATTRIBUTE= "name"; //$NON-NLS-1$ + private static final String ID_ATTRIBUTE= "id"; //$NON-NLS-1$ + private static final String DESCRIPTION_ATTRIBUTE= "description"; //$NON-NLS-1$ + private static final String CONTEXT_ATTRIBUTE= "context"; //$NON-NLS-1$ + private static final String ENABLED_ATTRIBUTE= "enabled"; //$NON-NLS-1$ + private static final String DELETED_ATTRIBUTE= "deleted"; //$NON-NLS-1$ + /** + * @since 3.1 + */ + private static final String AUTO_INSERTABLE_ATTRIBUTE= "autoinsert"; //$NON-NLS-1$ + + /** + * Create a new instance. + */ + public TemplateReaderWriter() { + } + + /** + * Reads templates from a reader and returns them. The reader must present + * a serialized form as produced by the <code>save</code> method. + * + * @param reader the reader to read templates from + * @return the read templates, encapsulated in instances of <code>TemplatePersistenceData</code> + * @throws IOException if reading from the stream fails + */ + public TemplatePersistenceData[] read(Reader reader) throws IOException { + return read(reader, null); + } + + /** + * Reads the template with identifier <code>id</code> from a reader and + * returns it. The reader must present a serialized form as produced by the + * <code>save</code> method. + * + * @param reader the reader to read templates from + * @param id the id of the template to return + * @return the read template, encapsulated in an instances of + * <code>TemplatePersistenceData</code> + * @throws IOException if reading from the stream fails + * @since 3.1 + */ + public TemplatePersistenceData readSingle(Reader reader, String id) throws IOException { + TemplatePersistenceData[] datas= read(new InputSource(reader), null, id); + if (datas.length > 0) + return datas[0]; + return null; + } + + /** + * Reads templates from a stream and adds them to the templates. + * + * @param reader the reader to read templates from + * @param bundle a resource bundle to use for translating the read templates, or <code>null</code> if no translation should occur + * @return the read templates, encapsulated in instances of <code>TemplatePersistenceData</code> + * @throws IOException if reading from the stream fails + */ + public TemplatePersistenceData[] read(Reader reader, ResourceBundle bundle) throws IOException { + return read(new InputSource(reader), bundle, null); + } + + /** + * Reads templates from a stream and adds them to the templates. + * + * @param stream the byte stream to read templates from + * @param bundle a resource bundle to use for translating the read templates, or <code>null</code> if no translation should occur + * @return the read templates, encapsulated in instances of <code>TemplatePersistenceData</code> + * @throws IOException if reading from the stream fails + */ + public TemplatePersistenceData[] read(InputStream stream, ResourceBundle bundle) throws IOException { + return read(new InputSource(stream), bundle, null); + } + + /** + * Reads templates from an <code>InputSource</code> and adds them to the templates. + * + * @param source the input source + * @param bundle a resource bundle to use for translating the read templates, or <code>null</code> if no translation should occur + * @param singleId the template id to extract, or <code>null</code> to read in all templates + * @return the read templates, encapsulated in instances of <code>TemplatePersistenceData</code> + * @throws IOException if reading from the stream fails + */ + private TemplatePersistenceData[] read(InputSource source, ResourceBundle bundle, String singleId) throws IOException { + try { + Collection<TemplatePersistenceData> templates= new ArrayList<>(); + Set<String> ids= new HashSet<>(); + + DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance(); + DocumentBuilder parser= factory.newDocumentBuilder(); + parser.setErrorHandler(new DefaultHandler()); + Document document= parser.parse(source); + + NodeList elements= document.getElementsByTagName(TEMPLATE_ELEMENT); + + int count= elements.getLength(); + for (int i= 0; i != count; i++) { + Node node= elements.item(i); + NamedNodeMap attributes= node.getAttributes(); + + if (attributes == null) + continue; + + String id= getStringValue(attributes, ID_ATTRIBUTE, null); + if (id != null && ids.contains(id)) { + String PLUGIN_ID= "org.eclipse.jface.text"; //$NON-NLS-1$ + ILog log= Platform.getLog(Platform.getBundle(PLUGIN_ID)); + String message= NLS.bind(TextTemplateMessages.getString("TemplateReaderWriter.duplicate.id"), id); //$NON-NLS-1$ + log.log(new Status(IStatus.WARNING, PLUGIN_ID, IStatus.OK, message, null)); + } else { + ids.add(id); + } + + if (singleId != null && !singleId.equals(id)) + continue; + + boolean deleted = getBooleanValue(attributes, DELETED_ATTRIBUTE, false); + + String name= getStringValue(attributes, NAME_ATTRIBUTE); + name= translateString(name, bundle); + + String description= getStringValue(attributes, DESCRIPTION_ATTRIBUTE, ""); //$NON-NLS-1$ + description= translateString(description, bundle); + + String context= getStringValue(attributes, CONTEXT_ATTRIBUTE); + + if (name == null || context == null) + throw new IOException(TextTemplateMessages.getString("TemplateReaderWriter.error.missing_attribute")); //$NON-NLS-1$ + + boolean enabled = getBooleanValue(attributes, ENABLED_ATTRIBUTE, true); + boolean autoInsertable= getBooleanValue(attributes, AUTO_INSERTABLE_ATTRIBUTE, true); + + StringBuilder buffer= new StringBuilder(); + NodeList children= node.getChildNodes(); + for (int j= 0; j != children.getLength(); j++) { + String value= children.item(j).getNodeValue(); + if (value != null) + buffer.append(value); + } + String pattern= buffer.toString(); + pattern= translateString(pattern, bundle); + + Template template= new Template(name, description, context, pattern, autoInsertable); + TemplatePersistenceData data= new TemplatePersistenceData(template, enabled, id); + data.setDeleted(deleted); + + templates.add(data); + + if (singleId != null && singleId.equals(id)) + break; + } + + return templates.toArray(new TemplatePersistenceData[templates.size()]); + + } catch (ParserConfigurationException e) { + Assert.isTrue(false); + } catch (SAXException e) { + throw (IOException)new IOException("Could not read template file").initCause(e); //$NON-NLS-1$ + } + + return null; // dummy + } + + /** + * Saves the templates as XML, encoded as UTF-8 onto the given byte stream. + * + * @param templates the templates to save + * @param stream the byte output to write the templates to in XML + * @throws IOException if writing the templates fails + */ + public void save(TemplatePersistenceData[] templates, OutputStream stream) throws IOException { + save(templates, new StreamResult(stream)); + } + + /** + * Saves the templates as XML. + * + * @param templates the templates to save + * @param writer the writer to write the templates to in XML + * @throws IOException if writing the templates fails + */ + public void save(TemplatePersistenceData[] templates, Writer writer) throws IOException { + save(templates, new StreamResult(writer)); + } + + /** + * Saves the templates as XML. + * + * @param templates the templates to save + * @param result the stream result to write to + * @throws IOException if writing the templates fails + */ + private void save(TemplatePersistenceData[] templates, StreamResult result) throws IOException { + try { + DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance(); + DocumentBuilder builder= factory.newDocumentBuilder(); + Document document= builder.newDocument(); + + Node root= document.createElement(TEMPLATE_ROOT); + document.appendChild(root); + + for (TemplatePersistenceData data : templates) { + Template template= data.getTemplate(); + + Node node= document.createElement(TEMPLATE_ELEMENT); + root.appendChild(node); + + NamedNodeMap attributes= node.getAttributes(); + + String id= data.getId(); + if (id != null) { + Attr idAttr= document.createAttribute(ID_ATTRIBUTE); + idAttr.setValue(id); + attributes.setNamedItem(idAttr); + } + + if (template != null) { + Attr name= document.createAttribute(NAME_ATTRIBUTE); + name.setValue(validateXML(template.getName())); + attributes.setNamedItem(name); + } + + if (template != null) { + Attr description= document.createAttribute(DESCRIPTION_ATTRIBUTE); + description.setValue(validateXML(template.getDescription())); + attributes.setNamedItem(description); + } + + if (template != null) { + Attr context= document.createAttribute(CONTEXT_ATTRIBUTE); + context.setValue(validateXML(template.getContextTypeId())); + attributes.setNamedItem(context); + } + + Attr enabled= document.createAttribute(ENABLED_ATTRIBUTE); + enabled.setValue(data.isEnabled() ? Boolean.toString(true) : Boolean.toString(false)); + attributes.setNamedItem(enabled); + + Attr deleted= document.createAttribute(DELETED_ATTRIBUTE); + deleted.setValue(data.isDeleted() ? Boolean.toString(true) : Boolean.toString(false)); + attributes.setNamedItem(deleted); + + if (template != null) { + Attr autoInsertable= document.createAttribute(AUTO_INSERTABLE_ATTRIBUTE); + autoInsertable.setValue(template.isAutoInsertable() ? Boolean.toString(true) : Boolean.toString(false)); + attributes.setNamedItem(autoInsertable); + } + + if (template != null) { + Text pattern= document.createTextNode(validateXML(template.getPattern())); + node.appendChild(pattern); + } + } + + + Transformer transformer=TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ + DOMSource source = new DOMSource(document); + + transformer.transform(source, result); + + } catch (ParserConfigurationException e) { + Assert.isTrue(false); + } catch (TransformerException e) { + if (e.getException() instanceof IOException) + throw (IOException) e.getException(); + Assert.isTrue(false); + } + } + + /** + * Validates whether the given string only contains valid XML characters. + * + * @param string the string to validate + * @return the input string + * @throws IOException when the first invalid character is detected + * @since 3.6 + */ + private static String validateXML(String string) throws IOException { + for (int i= 0; i < string.length(); i++) { + char ch= string.charAt(i); + if (!(ch == 9 || ch == 10 || ch == 13 || ch >= 32)) + throw new IOException("Character reference \"&#" + Integer.toString(ch) + "\" is an invalid XML character."); //$NON-NLS-1$ //$NON-NLS-2$ + } + return string; + } + + private boolean getBooleanValue(NamedNodeMap attributes, String attribute, boolean defaultValue) throws SAXException { + Node enabledNode= attributes.getNamedItem(attribute); + if (enabledNode == null) + return defaultValue; + else if (enabledNode.getNodeValue().equals(Boolean.toString(true))) + return true; + else if (enabledNode.getNodeValue().equals(Boolean.toString(false))) + return false; + else + throw new SAXException(TextTemplateMessages.getString("TemplateReaderWriter.error.illegal_boolean_attribute")); //$NON-NLS-1$ + } + + private String getStringValue(NamedNodeMap attributes, String name) throws SAXException { + String val= getStringValue(attributes, name, null); + if (val == null) + throw new SAXException(TextTemplateMessages.getString("TemplateReaderWriter.error.missing_attribute")); //$NON-NLS-1$ + return val; + } + + private String getStringValue(NamedNodeMap attributes, String name, String defaultValue) { + Node node= attributes.getNamedItem(name); + return node == null ? defaultValue : node.getNodeValue(); + } + + private String translateString(String str, ResourceBundle bundle) { + if (bundle == null) + return str; + + int idx= str.indexOf('%'); + if (idx == -1) { + return str; + } + StringBuilder buf= new StringBuilder(); + int k= 0; + while (idx != -1) { + buf.append(str.substring(k, idx)); + for (k= idx + 1; k < str.length() && !Character.isWhitespace(str.charAt(k)); k++) { + // loop + } + String key= str.substring(idx + 1, k); + buf.append(getBundleString(key, bundle)); + idx= str.indexOf('%', k); + } + buf.append(str.substring(k)); + return buf.toString(); + } + + private String getBundleString(String key, ResourceBundle bundle) { + if (bundle != null) { + try { + return bundle.getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } + return TextTemplateMessages.getString(key); // default messages + } +} + diff --git a/org.eclipse.text/src/org/eclipse/text/templates/TemplateStoreCore.java b/org.eclipse.text/src/org/eclipse/text/templates/TemplateStoreCore.java new file mode 100644 index 00000000000..a83edce2d7c --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/text/templates/TemplateStoreCore.java @@ -0,0 +1,504 @@ +/******************************************************************************* + * Copyright (c) 2000, 2018 IBM Corporation 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: + * 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; + } + +} + diff --git a/org.eclipse.text/src/org/eclipse/text/templates/TextTemplateMessages.java b/org.eclipse.text/src/org/eclipse/text/templates/TextTemplateMessages.java new file mode 100644 index 00000000000..2d032891f94 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/text/templates/TextTemplateMessages.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2000, 2018 IBM Corporation 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: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.templates; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import com.ibm.icu.text.MessageFormat; + +/* + * @since 3.0 + */ +class TextTemplateMessages { + + private static final String RESOURCE_BUNDLE= TextTemplateMessages.class.getName(); + private static ResourceBundle fgResourceBundle= ResourceBundle.getBundle(RESOURCE_BUNDLE); + + private TextTemplateMessages() { + } + + public static String getString(String key) { + try { + return fgResourceBundle.getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } + + public static String getFormattedString(String key, Object arg) { + return MessageFormat.format(getString(key), new Object[] { arg }); + } + + + public static String getFormattedString(String key, Object[] args) { + return MessageFormat.format(getString(key), args); + } +} diff --git a/org.eclipse.text/src/org/eclipse/text/templates/TextTemplateMessages.properties b/org.eclipse.text/src/org/eclipse/text/templates/TextTemplateMessages.properties new file mode 100644 index 00000000000..51e4947ea7c --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/text/templates/TextTemplateMessages.properties @@ -0,0 +1,14 @@ +############################################################################### +# Copyright (c) 2000, 2016 IBM Corporation 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: +# IBM Corporation - initial API and implementation +############################################################################### + +TemplateReaderWriter.duplicate.id= Duplicate template id: ''{0}'' +TemplateReaderWriter.error.missing_attribute= Missing required attribute +TemplateReaderWriter.error.illegal_boolean_attribute= Illegal boolean attribute, must be "true" or "false". |