diff options
author | Mickael Istria | 2017-07-27 08:40:05 +0000 |
---|---|---|
committer | Mickael Istria | 2017-08-23 14:02:22 +0000 |
commit | 85b23928b9d76e3b2ed9aeca2df0c520a79040ca (patch) | |
tree | ed5e79ed9e84ddfdead54b0f65441e1861f12605 | |
parent | 5f4c9dc850a8cb72d6e1f9173ddb46940d277913 (diff) | |
download | eclipse.platform.runtime-85b23928b9d76e3b2ed9aeca2df0c520a79040ca.tar.gz eclipse.platform.runtime-85b23928b9d76e3b2ed9aeca2df0c520a79040ca.tar.xz eclipse.platform.runtime-85b23928b9d76e3b2ed9aeca2df0c520a79040ca.zip |
Bug 263316 - Filename patterns for content typesI20170906-0225I20170905-2000I20170905-0600I20170905-0310I20170904-2000I20170904-0230I20170903-2000I20170902-1500I20170901-2000I20170831-2000I20170830-2000I20170830-0605I20170829-2345I20170829-2000I20170829-0940I20170829-0705I20170828-2000I20170828-1220I20170828-0730I20170827-2000I20170826-1500I20170825-2000I20170824-2000I20170823-2000
Change-Id: I6e890f5b284e48b204a1c4fe860e64d88f25a641
Signed-off-by: Mickael Istria <mistria@redhat.com>
11 files changed, 304 insertions, 109 deletions
diff --git a/bundles/org.eclipse.core.contenttype/.settings/.api_filters b/bundles/org.eclipse.core.contenttype/.settings/.api_filters index 856259830..97dfc21a7 100644 --- a/bundles/org.eclipse.core.contenttype/.settings/.api_filters +++ b/bundles/org.eclipse.core.contenttype/.settings/.api_filters @@ -1,16 +1,16 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <component id="org.eclipse.core.contenttype" version="2"> - <resource path="src/org/eclipse/core/runtime/content/IContentTypeManager.java" type="org.eclipse.core.runtime.content.IContentTypeManager"> - <filter comment="https://bugs.eclipse.org/bugs/show_bug.cgi?id=57908#c37" id="403853384"> + <resource path="src/org/eclipse/core/runtime/content/IContentType.java" type="org.eclipse.core.runtime.content.IContentType"> + <filter comment="The Javadoc comment was replaced by proper API Tools tag" id="403853384"> <message_arguments> - <message_argument value="org.eclipse.core.runtime.content.IContentTypeManager"/> + <message_argument value="org.eclipse.core.runtime.content.IContentType"/> </message_arguments> </filter> </resource> - <resource path="src/org/eclipse/core/runtime/content/IContentTypeSettings.java" type="org.eclipse.core.runtime.content.IContentTypeSettings"> + <resource path="src/org/eclipse/core/runtime/content/IContentTypeManager.java" type="org.eclipse.core.runtime.content.IContentTypeManager"> <filter comment="https://bugs.eclipse.org/bugs/show_bug.cgi?id=57908#c37" id="403853384"> <message_arguments> - <message_argument value="org.eclipse.core.runtime.content.IContentTypeSettings"/> + <message_argument value="org.eclipse.core.runtime.content.IContentTypeManager"/> </message_arguments> </filter> </resource> diff --git a/bundles/org.eclipse.core.contenttype/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.contenttype/META-INF/MANIFEST.MF index 039b12b2c..c54f4d484 100644 --- a/bundles/org.eclipse.core.contenttype/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.core.contenttype/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.contenttype; singleton:=true -Bundle-Version: 3.6.100.qualifier +Bundle-Version: 3.7.0.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Require-Bundle: org.eclipse.equinox.preferences;bundle-version="[3.2.0,4.0.0)", diff --git a/bundles/org.eclipse.core.contenttype/pom.xml b/bundles/org.eclipse.core.contenttype/pom.xml index 1fa77a25b..bb2c76a36 100644 --- a/bundles/org.eclipse.core.contenttype/pom.xml +++ b/bundles/org.eclipse.core.contenttype/pom.xml @@ -19,6 +19,6 @@ </parent> <groupId>org.eclipse.core</groupId> <artifactId>org.eclipse.core.contenttype</artifactId> - <version>3.6.100-SNAPSHOT</version> + <version>3.7.0-SNAPSHOT</version> <packaging>eclipse-plugin</packaging> </project> diff --git a/bundles/org.eclipse.core.contenttype/schema/contentTypes.exsd b/bundles/org.eclipse.core.contenttype/schema/contentTypes.exsd index 85a0b3dc2..59d5c3011 100644 --- a/bundles/org.eclipse.core.contenttype/schema/contentTypes.exsd +++ b/bundles/org.eclipse.core.contenttype/schema/contentTypes.exsd @@ -2,9 +2,9 @@ <!-- Schema file written by PDE --> <schema targetNamespace="org.eclipse.core.contenttype" xmlns="http://www.w3.org/2001/XMLSchema"> <annotation> - <appinfo> + <appInfo> <meta.schema plugin="org.eclipse.core.contenttype" id="contentTypes" name="Content Types"/> - </appinfo> + </appInfo> <documentation> The content types extension point allows plug-ins to contribute to the platform content type catalog. There are two forms of contributions: <cite>content types</cite> and <cite>file associations</cite>. <ul> @@ -24,9 +24,9 @@ a file association extends an existing content type by associating new file name <element name="extension"> <annotation> - <appinfo> + <appInfo> <meta.element /> - </appinfo> + </appInfo> </annotation> <complexType> <sequence> @@ -52,9 +52,9 @@ a file association extends an existing content type by associating new file name <documentation> an optional name of the extension instance </documentation> - <appinfo> + <appInfo> <meta.attribute translatable="true"/> - </appinfo> + </appInfo> </annotation> </attribute> </complexType> @@ -85,9 +85,9 @@ a file association extends an existing content type by associating new file name <documentation> the human-readable name of this content type </documentation> - <appinfo> + <appInfo> <meta.attribute translatable="true"/> - </appinfo> + </appInfo> </annotation> </attribute> <attribute name="file-extensions" type="string"> @@ -104,6 +104,13 @@ a file association extends an existing content type by associating new file name </documentation> </annotation> </attribute> + <attribute name="file-patterns" type="string"> + <annotation> + <documentation> + a comma-separated list of file name patterns to be associated with this content type. Since 3.7 + </documentation> + </annotation> + </attribute> <attribute name="priority" use="default" value="normal"> <annotation> <documentation> @@ -138,9 +145,9 @@ a file association extends an existing content type by associating new file name <documentation> the fully qualified name of a class that implements <samp>org.eclipse.core.runtime.content.IContentDescriber</samp> or <samp>org.eclipse.core.runtime.content.ITextContentDescriber</samp>, or an empty string, if this content type should not have a describer even if the parent has one </documentation> - <appinfo> + <appInfo> <meta.attribute kind="java"/> - </appinfo> + </appInfo> </annotation> </attribute> <attribute name="alias-for" type="string"> @@ -168,9 +175,9 @@ a file association extends an existing content type by associating new file name <documentation> the fully qualified name of a class that implements <samp>org.eclipse.core.runtime.content.IContentDescriber</samp> or <samp>org.eclipse.core.runtime.content.ITextContentDescriber</samp>, or an empty string, if this content type should not have a describer even if the parent has one </documentation> - <appinfo> + <appInfo> <meta.attribute kind="java" basedOn="org.eclipse.core.runtime.content.IContentDescriber"/> - </appinfo> + </appInfo> </annotation> </attribute> <attribute name="plugin" type="string"> @@ -190,6 +197,9 @@ a file association extends an existing content type by associating new file name <documentation> the fully qualified identifier for the content type this file association contributes to </documentation> + <appInfo> + <meta.attribute kind="identifier" basedOn="org.eclipse.core.contenttype.contentTypes/content-type/@id"/> + </appInfo> </annotation> </attribute> <attribute name="file-names" type="string"> @@ -206,6 +216,13 @@ a file association extends an existing content type by associating new file name </documentation> </annotation> </attribute> + <attribute name="file-patterns" type="string"> + <annotation> + <documentation> + a comma-separated list of file name patterns to be associated with this content type. Since 3.7 + </documentation> + </annotation> + </attribute> </complexType> </element> @@ -253,18 +270,18 @@ a file association extends an existing content type by associating new file name </element> <annotation> - <appinfo> + <appInfo> <meta.section type="since"/> - </appinfo> + </appInfo> <documentation> 3.2 </documentation> </annotation> <annotation> - <appinfo> + <appInfo> <meta.section type="examples"/> - </appinfo> + </appInfo> <documentation> Following is an example of a XML-based content type declaration using <code>org.eclipse.core.runtime.content.XMLRootElementContentDescriber2</code>, a built-in describer: @@ -315,9 +332,9 @@ Here is an example of a content type that defines properties: </annotation> <annotation> - <appinfo> + <appInfo> <meta.section type="apiInfo"/> - </appinfo> + </appInfo> <documentation> The value of the class attribute in the describer element must represent an implementor of @@ -328,9 +345,9 @@ implementor of </annotation> <annotation> - <appinfo> + <appInfo> <meta.section type="implementation"/> - </appinfo> + </appInfo> <documentation> <p>The org.eclipse.core.contenttype plug-in provides the following content types:<ul> <li>org.eclipse.core.runtime.text</li> @@ -348,9 +365,9 @@ Also, the org.eclipse.core.contenttype plug-in provides ready-to-use implementat </annotation> <annotation> - <appinfo> + <appInfo> <meta.section type="copyright"/> - </appinfo> + </appInfo> <documentation> Copyright (c) 2004, 2008 IBM Corporation and others.<br> All rights reserved. This program and the accompanying materials are made diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java index 136feaa3c..66c615c97 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentType.java @@ -51,6 +51,8 @@ public final class ContentType implements IContentType, IContentTypeInfo { public final static String PREF_DEFAULT_CHARSET = "charset"; //$NON-NLS-1$ public final static String PREF_FILE_EXTENSIONS = "file-extensions"; //$NON-NLS-1$ public final static String PREF_FILE_NAMES = "file-names"; //$NON-NLS-1$ + /** @since 3.7 */ + public final static String PREF_FILE_PATTERNS = "file-patterns"; //$NON-NLS-1$ /** @since 3.6 */ public static final String PREF_USER_DEFINED = "userDefined"; //$NON-NLS-1$ /** @since 3.6 */ @@ -89,20 +91,26 @@ public final class ContentType implements IContentType, IContentTypeInfo { // -1 means unknown private byte depth = -1; - public static ContentType createContentType(ContentTypeCatalog catalog, String uniqueId, String name, byte priority, String[] fileExtensions, String[] fileNames, String baseTypeId, String aliasTargetId, Map<QualifiedName, String> defaultProperties, IConfigurationElement contentTypeElement) { + public static ContentType createContentType(ContentTypeCatalog catalog, String uniqueId, String name, byte priority, + String[] fileExtensions, String[] fileNames, String[] filePatterns, String baseTypeId, String aliasTargetId, + Map<QualifiedName, String> defaultProperties, IConfigurationElement contentTypeElement) { ContentType contentType = new ContentType(catalog.getManager()); contentType.catalog = catalog; contentType.defaultDescription = new DefaultDescription(contentType); contentType.id = uniqueId; contentType.name = name; contentType.priority = priority; - if ((fileExtensions != null && fileExtensions.length > 0) || (fileNames != null && fileNames.length > 0)) { + if ((fileExtensions != null && fileExtensions.length > 0) || (fileNames != null && fileNames.length > 0) + || (filePatterns != null && filePatterns.length > 0)) { contentType.builtInAssociations = true; - contentType.fileSpecs = new ArrayList<>(fileExtensions.length + fileNames.length); + contentType.fileSpecs = new ArrayList<>(fileExtensions.length + fileNames.length + filePatterns.length); for (String fileName : fileNames) contentType.internalAddFileSpec(fileName, FILE_NAME_SPEC | SPEC_PRE_DEFINED); for (String fileExtension : fileExtensions) contentType.internalAddFileSpec(fileExtension, FILE_EXTENSION_SPEC | SPEC_PRE_DEFINED); + for (String fileExtension : filePatterns) { + contentType.internalAddFileSpec(fileExtension, FILE_PATTERN_SPEC | SPEC_PRE_DEFINED); + } } contentType.defaultProperties = defaultProperties; contentType.contentTypeElement = contentTypeElement; @@ -120,6 +128,8 @@ public final class ContentType implements IContentType, IContentTypeInfo { return PREF_FILE_EXTENSIONS; if ((flags & FILE_NAME_SPEC) != 0) return PREF_FILE_NAMES; + if ((flags & FILE_PATTERN_SPEC) != 0) + return PREF_FILE_PATTERNS; throw new IllegalArgumentException("Unknown type: " + flags); //$NON-NLS-1$ } @@ -139,7 +149,8 @@ public final class ContentType implements IContentType, IContentTypeInfo { @Override public void addFileSpec(String fileSpec, int type) throws CoreException { - Assert.isLegal(type == FILE_EXTENSION_SPEC || type == FILE_NAME_SPEC, "Unknown type: " + type); //$NON-NLS-1$ + Assert.isLegal(type == FILE_EXTENSION_SPEC || type == FILE_NAME_SPEC || type == FILE_PATTERN_SPEC, + "Unknown type: " + type); //$NON-NLS-1$ String[] userSet; synchronized (this) { if (!internalAddFileSpec(fileSpec, type | SPEC_USER_DEFINED)) @@ -377,8 +388,10 @@ public final class ContentType implements IContentType, IContentTypeInfo { /** * Returns whether this content type has the given file spec. * - * @param text the file spec string - * @param typeMask FILE_NAME_SPEC or FILE_EXTENSION_SPEC + * @param text + * the file spec string + * @param typeMask + * FILE_NAME_SPEC or FILE_EXTENSION_SPEC or FILE_REGEXP_SPEC * @param strict * @return true if this file spec has already been added, false otherwise */ @@ -542,11 +555,18 @@ public final class ContentType implements IContentType, IContentTypeInfo { String[] fileExtensions = Util.parseItems(userSetFileExtensions); for (String fileExtension : fileExtensions) internalAddFileSpec(fileExtension, FILE_EXTENSION_SPEC | SPEC_USER_DEFINED); + // user set file name regexp + String userSetFileRegexp = contentTypeNode.get(PREF_FILE_PATTERNS, null); + String[] fileRegexps = Util.parseItems(userSetFileRegexp); + for (String fileRegexp : fileRegexps) { + internalAddFileSpec(fileRegexp, FILE_PATTERN_SPEC | SPEC_USER_DEFINED); + } } @Override public void removeFileSpec(String fileSpec, int type) throws CoreException { - Assert.isLegal(type == FILE_EXTENSION_SPEC || type == FILE_NAME_SPEC, "Unknown type: " + type); //$NON-NLS-1$ + Assert.isLegal(type == FILE_EXTENSION_SPEC || type == FILE_NAME_SPEC || type == FILE_PATTERN_SPEC, + "Unknown type: " + type); //$NON-NLS-1$ synchronized (this) { if (!internalRemoveFileSpec(fileSpec, type | SPEC_USER_DEFINED)) return; diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeBuilder.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeBuilder.java index 2dfcb9fa9..4d688cb5d 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeBuilder.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeBuilder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2009 IBM Corporation and others. + * Copyright (c) 2004, 2017 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 @@ -7,6 +7,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Mickael Istria (Red Hat Inc.) - [263316] regexp for file association *******************************************************************************/ package org.eclipse.core.internal.content; @@ -77,6 +78,9 @@ public class ContentTypeBuilder { String[] fileExtensions = Util.parseItems(fileAssociationElement.getAttribute("file-extensions")); //$NON-NLS-1$ for (String fileExtension : fileExtensions) target.internalAddFileSpec(fileExtension, IContentType.FILE_EXTENSION_SPEC | ContentType.SPEC_PRE_DEFINED); + String[] filePatterns = Util.parseItems(fileAssociationElement.getAttribute("file-patterns")); //$NON-NLS-1$ + for (String filePattern : filePatterns) + target.internalAddFileSpec(filePattern, IContentType.FILE_PATTERN_SPEC | ContentType.SPEC_PRE_DEFINED); } /** @@ -91,7 +95,8 @@ public class ContentTypeBuilder { IEclipsePreferences node = context.getNode(id); catalog.addContentType(ContentType.createContentType(catalog, id, node.get(ContentType.PREF_USER_DEFINED__NAME, ContentType.EMPTY_STRING), - (byte) 0, new String[0], new String[0], node.get(ContentType.PREF_USER_DEFINED__BASE_TYPE_ID, null), null, Collections.emptyMap(), + (byte) 0, new String[0], new String[0], new String[0], + node.get(ContentType.PREF_USER_DEFINED__BASE_TYPE_ID, null), null, Collections.emptyMap(), null)); } for (IConfigurationElement allContentTypeCE : allContentTypeCEs) @@ -145,6 +150,7 @@ public class ContentTypeBuilder { byte priority = parsePriority(contentTypeCE.getAttribute("priority")); //$NON-NLS-1$ ); String[] fileNames = Util.parseItems(contentTypeCE.getAttribute("file-names")); //$NON-NLS-1$ String[] fileExtensions = Util.parseItems(contentTypeCE.getAttribute("file-extensions")); //$NON-NLS-1$ + String[] filePatterns = Util.parseItems(contentTypeCE.getAttribute("file-patterns")); //$NON-NLS-1$ String baseTypeId = getUniqueId(namespace, contentTypeCE.getAttribute("base-type")); //$NON-NLS-1$ String aliasTargetTypeId = getUniqueId(namespace, contentTypeCE.getAttribute("alias-for")); //$NON-NLS-1$ IConfigurationElement[] propertyCEs = null; @@ -174,8 +180,8 @@ public class ContentTypeBuilder { defaultProperties = Collections.singletonMap(IContentDescription.CHARSET, defaultCharset); else if (!defaultProperties.containsKey(IContentDescription.CHARSET)) defaultProperties.put(IContentDescription.CHARSET, defaultCharset); - return ContentType.createContentType(catalog, uniqueId, name, priority, fileExtensions, fileNames, baseTypeId, - aliasTargetTypeId, defaultProperties, contentTypeCE); + return ContentType.createContentType(catalog, uniqueId, name, priority, fileExtensions, fileNames, filePatterns, + baseTypeId, aliasTargetTypeId, defaultProperties, contentTypeCE); } // Store this around for performance diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java index 5f23e1f31..5a53975b6 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeCatalog.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2004, 2016 IBM Corporation and others. + * Copyright (c) 2004, 2017 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 @@ -7,11 +7,14 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Mickael Istria (Red Hat Inc.) - [263316] regexp for file association *******************************************************************************/ package org.eclipse.core.internal.content; import java.io.*; import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.content.*; import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy; @@ -28,6 +31,9 @@ public final class ContentTypeCatalog { private final Map<String, IContentType> contentTypes = new HashMap<>(); private final Map<String, Set<ContentType>> fileExtensions = new HashMap<>(); private final Map<String, Set<ContentType>> fileNames = new HashMap<>(); + private final Map<String, Pattern> compiledRegexps = new HashMap<>(); + private final Map<Pattern, String> initialPatternForRegexp = new HashMap<>(); + private final Map<Pattern, Set<ContentType>> fileRegexps = new HashMap<>(); private int generation; private ContentTypeManager manager; @@ -127,13 +133,23 @@ public final class ContentTypeCatalog { }; private static IContentType[] concat(IContentType[][] types) { - if (types[0].length == 0) - return types[1]; - if (types[1].length == 0) - return types[0]; - IContentType[] result = new IContentType[types[0].length + types[1].length]; - System.arraycopy(types[0], 0, result, 0, types[0].length); - System.arraycopy(types[1], 0, result, types[0].length, types[1].length); + int size = 0; + IContentType[] nonEmptyOne = NO_CONTENT_TYPES; + for (IContentType[] array : types) { + size += array.length; + if (array.length > 0) { + nonEmptyOne = array; + } + } + if (nonEmptyOne.length == size) { // no other array has content + return nonEmptyOne; + } + IContentType[] result = new IContentType[size]; + int currentIndex = 0; + for (IContentType[] array : types) { + System.arraycopy(array, 0, result, currentIndex, array.length); + currentIndex += array.length; + } return result; } @@ -174,15 +190,40 @@ public final class ContentTypeCatalog { String[] builtInFileExtensions = contentType.getFileSpecs(IContentType.IGNORE_USER_DEFINED | IContentType.FILE_EXTENSION_SPEC); for (String builtInFileExtension : builtInFileExtensions) associate(contentType, builtInFileExtension, IContentType.FILE_EXTENSION_SPEC); + String[] builtInFilePatterns = contentType + .getFileSpecs(IContentType.IGNORE_USER_DEFINED | IContentType.FILE_PATTERN_SPEC); + for (String builtInFilePattern : builtInFilePatterns) { + associate(contentType, builtInFilePattern, IContentType.FILE_PATTERN_SPEC); + } + } + + String toRegexp(String filePattern) { + return filePattern.replace(".", "\\.").replace('?', '.').replace("*", ".*"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } synchronized void associate(ContentType contentType, String text, int type) { - Map<String, Set<ContentType>> fileSpecMap = ((type & IContentType.FILE_NAME_SPEC) != 0) ? fileNames : fileExtensions; - String mappingKey = FileSpec.getMappingKeyFor(text); - Set<ContentType> existing = fileSpecMap.get(mappingKey); - if (existing == null) - fileSpecMap.put(mappingKey, existing = new HashSet<>()); - existing.add(contentType); + Map<String, Set<ContentType>> fileSpecMap = null; + if ((type & IContentType.FILE_NAME_SPEC) != 0) { + fileSpecMap = fileNames; + } else if ((type & IContentType.FILE_EXTENSION_SPEC) != 0) { + fileSpecMap = fileExtensions; + } + if (fileSpecMap != null) { + String mappingKey = FileSpec.getMappingKeyFor(text); + Set<ContentType> existing = fileSpecMap.get(mappingKey); + if (existing == null) + fileSpecMap.put(mappingKey, existing = new HashSet<>()); + existing.add(contentType); + } else if ((type & IContentType.FILE_PATTERN_SPEC) != 0) { + Pattern compiledPattern = compiledRegexps.get(text); + if (compiledPattern == null) { + compiledPattern = Pattern.compile(toRegexp(text)); + compiledRegexps.put(text, compiledPattern); + initialPatternForRegexp.put(compiledPattern, text); + fileRegexps.put(compiledPattern, new HashSet<>()); + } + fileRegexps.get(compiledPattern).add(contentType); + } } private int collectMatchingByContents(int valid, IContentType[] subset, List<ContentType> destination, ILazySource contents, Map<String, Object> properties) throws IOException { @@ -410,15 +451,26 @@ public final class ContentTypeCatalog { final int appropriateFullName = appropriate.size(); final int validExtension = collectMatchingByContents(validFullName, subset[1], appropriate, buffer, properties) - validFullName; final int appropriateExtension = appropriate.size() - appropriateFullName; + final int validPattern = collectMatchingByContents(validExtension, subset[2], appropriate, buffer, properties) + - validExtension; + final int appropriatePattern = appropriate.size() - appropriateFullName - appropriateExtension; IContentType[] result = appropriate.toArray(new IContentType[appropriate.size()]); if (validFullName > 1) Arrays.sort(result, 0, validFullName, validPolicy); if (validExtension > 1) Arrays.sort(result, validFullName, validFullName + validExtension, validPolicy); + if (validPattern > 1) { + Arrays.sort(result, validFullName + validExtension, validFullName + validExtension + validPattern, + validPolicy); + } if (appropriateFullName - validFullName > 1) Arrays.sort(result, validFullName + validExtension, appropriateFullName + validExtension, indeterminatePolicy); if (appropriateExtension - validExtension > 1) - Arrays.sort(result, appropriateFullName + validExtension, appropriate.size(), indeterminatePolicy); + Arrays.sort(result, appropriateFullName + validExtension, appropriate.size() - validPattern, + indeterminatePolicy); + if (appropriatePattern - validPattern > 1) { + Arrays.sort(result, appropriate.size() - validPattern, appropriate.size(), indeterminatePolicy); + } return result; } @@ -427,8 +479,9 @@ public final class ContentTypeCatalog { final Comparator<IContentType> validPolicy; Comparator<IContentType> indeterminatePolicy; if (fileName == null) { - // we only have a single array, by need to provide a two-dimensional, 2-element array - subset = new IContentType[][] {getAllContentTypes(), NO_CONTENT_TYPES}; + // we only have a single array, by need to provide a two-dimensional, 3-element + // array + subset = new IContentType[][] { getAllContentTypes(), NO_CONTENT_TYPES, NO_CONTENT_TYPES }; indeterminatePolicy = policyConstantGeneralIsBetter; validPolicy = policyConstantSpecificIsBetter; } else { @@ -436,13 +489,13 @@ public final class ContentTypeCatalog { indeterminatePolicy = policyGeneralIsBetter; validPolicy = policySpecificIsBetter; } - int total = subset[0].length + subset[1].length; + int total = subset[0].length + subset[1].length + subset[2].length; if (total == 0) // don't do further work if subset is empty return NO_CONTENT_TYPES; if (!forceValidation && total == 1) { // do not do validation if not forced and only one was found (caller will validate later) - IContentType[] found = subset[0].length == 1 ? subset[0] : subset[1]; + IContentType[] found = subset[0].length == 1 ? subset[0] : (subset[1].length == 1 ? subset[1] : subset[2]); // bug 100032 - ignore binary content type if contents are text if (!buffer.isText()) // binary buffer, caller can call the describer with no risk @@ -466,10 +519,11 @@ public final class ContentTypeCatalog { */ synchronized private IContentType[][] internalFindContentTypesFor(ContentTypeMatcher matcher, final String fileName, Comparator<IContentType> sortingPolicy) { IScopeContext context = matcher.getContext(); - IContentType[][] result = {NO_CONTENT_TYPES, NO_CONTENT_TYPES}; + IContentType[][] result = { NO_CONTENT_TYPES, NO_CONTENT_TYPES, NO_CONTENT_TYPES }; - final Set<ContentType> allByFileName; + Set<ContentType> existing = new HashSet<>(); + final Set<ContentType> allByFileName; if (context.equals(manager.getContext())) allByFileName = getDirectlyAssociated(fileName, IContentTypeSettings.FILE_NAME_SPEC); else { @@ -478,7 +532,11 @@ public final class ContentTypeCatalog { } Set<ContentType> selectedByName = selectMatchingByName(context, allByFileName, Collections.emptySet(), fileName, IContentType.FILE_NAME_SPEC); + existing.addAll(selectedByName); result[0] = selectedByName.toArray(new IContentType[selectedByName.size()]); + if (result[0].length > 1) + Arrays.sort(result[0], sortingPolicy); + final String fileExtension = ContentTypeManager.getFileExtension(fileName); if (fileExtension != null) { final Set<ContentType> allByFileExtension; @@ -489,16 +547,44 @@ public final class ContentTypeCatalog { allByFileExtension.addAll(matcher.getDirectlyAssociated(this, fileExtension, IContentTypeSettings.FILE_EXTENSION_SPEC)); } Set<ContentType> selectedByExtension = selectMatchingByName(context, allByFileExtension, selectedByName, fileExtension, IContentType.FILE_EXTENSION_SPEC); + existing.addAll(selectedByExtension); if (!selectedByExtension.isEmpty()) result[1] = selectedByExtension.toArray(new IContentType[selectedByExtension.size()]); } - if (result[0].length > 1) - Arrays.sort(result[0], sortingPolicy); if (result[1].length > 1) Arrays.sort(result[1], sortingPolicy); + + final Set<ContentType> allByFilePattern; + if (context.equals(manager.getContext())) + allByFilePattern = getMatchingRegexpAssociated(fileName, IContentTypeSettings.FILE_PATTERN_SPEC); + else { + allByFilePattern = new HashSet<>(getMatchingRegexpAssociated(fileName, + IContentTypeSettings.FILE_PATTERN_SPEC | IContentType.IGNORE_USER_DEFINED)); + allByFilePattern + .addAll(matcher.getMatchingRegexpAssociated(this, fileName, + IContentTypeSettings.FILE_PATTERN_SPEC)); + } + existing.addAll(allByFilePattern); + if (!allByFilePattern.isEmpty()) + result[2] = allByFilePattern.toArray(new IContentType[allByFilePattern.size()]); + return result; } + private Set<ContentType> getMatchingRegexpAssociated(String fileName, int typeMask) { + if ((typeMask & IContentType.FILE_PATTERN_SPEC) == 0) { + throw new IllegalArgumentException("This method requires FILE_PATTERN_SPEC."); //$NON-NLS-1$ + } + Set<ContentType> res = new HashSet<>(); + for (Entry<Pattern, Set<ContentType>> spec : this.fileRegexps.entrySet()) { + if (spec.getKey().matcher(fileName).matches()) { + res.addAll(filterOnDefinitionSource(initialPatternForRegexp.get(spec.getKey()), typeMask, + spec.getValue())); + } + } + return res; + } + /** * Returns content types directly associated with the given file spec. * @@ -513,29 +599,50 @@ public final class ContentTypeCatalog { * @return a set of content types */ private Set<ContentType> getDirectlyAssociated(String text, int typeMask) { + if ((typeMask & IContentType.FILE_PATTERN_SPEC) != 0) { + throw new IllegalArgumentException("This method don't allow FILE_REGEXP_SPEC."); //$NON-NLS-1$ + } Map<String, Set<ContentType>> associations = (typeMask & IContentTypeSettings.FILE_NAME_SPEC) != 0 ? fileNames : fileExtensions; - Set<ContentType> result = null; - if ((typeMask & (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED)) == 0) - // no restrictions, get everything - result = associations.get(FileSpec.getMappingKeyFor(text)); - else { - // only those specs satisfying the type mask should be included - Set<ContentType> initialSet = associations.get(FileSpec.getMappingKeyFor(text)); - if (initialSet != null && !initialSet.isEmpty()) { - // copy so we can modify - result = new HashSet<>(initialSet); - // invert the last two bits so it is easier to compare - typeMask ^= (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED); - for (Iterator<ContentType> i = result.iterator(); i.hasNext();) { - ContentType contentType = i.next(); - if (!contentType.hasFileSpec(text, typeMask, true)) - i.remove(); - } - } + Set<ContentType> result = associations.get(FileSpec.getMappingKeyFor(text)); + if ((typeMask & (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED)) != 0) { + result = filterOnDefinitionSource(text, typeMask, result); } return result == null ? Collections.EMPTY_SET : result; } + /** + * Filters a set of content-types on whether they have a mapping that matches + * provided criteria. + * + * @param text + * file name, file extension or file regexp (depending on value of + * {@code typeMask}. + * @param typeMask + * the type mask. Spec type, and definition source (pre-defined or + * user-defined) will be used + * @param contentTypes + * content types to filter from (not modified during method + * execution) + * @return set of filtered content-type + */ + private Set<ContentType> filterOnDefinitionSource(String text, int typeMask, Set<ContentType> contentTypes) { + if ((typeMask & (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED)) == 0) { + return contentTypes; + } + if (contentTypes != null && !contentTypes.isEmpty()) { + // copy so we can modify + contentTypes = new HashSet<>(contentTypes); + // invert the last two bits so it is easier to compare + typeMask ^= (IContentType.IGNORE_PRE_DEFINED | IContentType.IGNORE_USER_DEFINED); + for (Iterator<ContentType> i = contentTypes.iterator(); i.hasNext();) { + ContentType contentType = i.next(); + if (!contentType.hasFileSpec(text, typeMask, true)) + i.remove(); + } + } + return contentTypes; + } + synchronized ContentType internalGetContentType(String contentTypeIdentifier) { return (ContentType) contentTypes.get(contentTypeIdentifier); } diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeManager.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeManager.java index 5b2158abf..ec0a5dd42 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeManager.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeManager.java @@ -265,7 +265,7 @@ public class ContentTypeManager extends ContentTypeMatcher implements IContentTy throw new IllegalArgumentException("Content-type '" + id + "' already exists.");//$NON-NLS-1$ //$NON-NLS-2$ } ContentType contentType = ContentType.createContentType(getCatalog(), id, name, (byte) 0, new String[0], - new String[0], baseType != null ? baseType.getId() : null, null, null, null); + new String[0], new String[0], baseType != null ? baseType.getId() : null, null, null, null); getCatalog().addContentType(contentType); // Add preferences for this content type. String currentUserDefined = getContext().getNode(ContentType.PREF_USER_DEFINED) diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeMatcher.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeMatcher.java index 89ba95256..6d26b35da 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeMatcher.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/internal/content/ContentTypeMatcher.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2005, 2007 IBM Corporation and others. + * Copyright (c) 2005, 2017 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 @@ -7,14 +7,17 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Mickael Istria (Red Hat Inc.) - [263316] regexp for file association *******************************************************************************/ package org.eclipse.core.internal.content; import java.io.*; import java.util.*; +import java.util.regex.Pattern; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.content.*; -import org.eclipse.core.runtime.preferences.*; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IScopeContext; import org.osgi.service.prefs.BackingStoreException; /** @@ -93,26 +96,52 @@ public class ContentTypeMatcher implements IContentTypeMatcher { * Enumerates all content types whose settings satisfy the given file spec type mask. */ public Collection<ContentType> getDirectlyAssociated(final ContentTypeCatalog catalog, final String fileSpec, final int typeMask) { + if ((typeMask & (IContentType.FILE_EXTENSION_SPEC | IContentType.FILE_NAME_SPEC)) == 0) { + throw new IllegalArgumentException("This method only apply to name or extension based associations"); //$NON-NLS-1$ + } //TODO: make sure we include built-in associations as well final IEclipsePreferences root = context.getNode(ContentTypeManager.CONTENT_TYPE_PREF_NODE); final Set<ContentType> result = new HashSet<>(3); try { - root.accept(new IPreferenceNodeVisitor() { - @Override - public boolean visit(IEclipsePreferences node) { - if (node == root) - return true; - String[] fileSpecs = ContentTypeSettings.getFileSpecs(node, typeMask); - for (String fileSpecification : fileSpecs) - if (fileSpecification.equalsIgnoreCase(fileSpec)) { - ContentType associated = catalog.getContentType(node.name()); - if (associated != null) - result.add(associated); - break; - } - return false; - } + root.accept(node -> { + if (node == root) + return true; + String[] fileSpecs = ContentTypeSettings.getFileSpecs(node, typeMask); + for (String fileSpecification : fileSpecs) + if (fileSpecification.equalsIgnoreCase(fileSpec)) { + ContentType associated = catalog.getContentType(node.name()); + if (associated != null) + result.add(associated); + break; + } + return false; + }); + } catch (BackingStoreException bse) { + ContentType.log(ContentMessages.content_errorLoadingSettings, bse); + } + return result == null ? Collections.EMPTY_SET : result; + } + public Collection<? extends ContentType> getMatchingRegexpAssociated(ContentTypeCatalog catalog, + String fileName, final int typeMask) { + if ((typeMask & IContentType.FILE_PATTERN_SPEC) == 0) { + throw new IllegalArgumentException("This method only applies for FILE_REGEXP_SPEC."); //$NON-NLS-1$ + } + final IEclipsePreferences root = context.getNode(ContentTypeManager.CONTENT_TYPE_PREF_NODE); + final Set<ContentType> result = new HashSet<>(3); + try { + root.accept(node -> { + if (node == root) + return true; + String[] fileSpecs = ContentTypeSettings.getFileSpecs(node, typeMask); + for (String fileSpecification : fileSpecs) + if (Pattern.matches(catalog.toRegexp(fileSpecification), fileName)) { + ContentType associated = catalog.getContentType(node.name()); + if (associated != null) + result.add(associated); + break; + } + return false; }); } catch (BackingStoreException bse) { ContentType.log(ContentMessages.content_errorLoadingSettings, bse); @@ -133,4 +162,5 @@ public class ContentTypeMatcher implements IContentTypeMatcher { ((ContentDescription) description).setContentTypeInfo(new ContentTypeSettings((ContentType) description.getContentTypeInfo(), context)); return description; } + } diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentType.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentType.java index 4fd5d2f9c..953ea0c67 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentType.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentType.java @@ -18,10 +18,8 @@ import org.eclipse.core.runtime.preferences.IScopeContext; /** * Content types represent and provide information on file types, such as * associated file names/extensions, default charset, etc. - * <p> - * This interface is not intended to be implemented by clients. - * </p> * + * @noimplement This interface is not intended to be implemented by clients. * @since 3.0 */ public interface IContentType extends IContentTypeSettings { @@ -29,20 +27,28 @@ public interface IContentType extends IContentTypeSettings { * File spec type flag constant, indicating that predefined file * specifications should not be taken into account. */ - public static final int IGNORE_PRE_DEFINED = 0x01; + public static final int IGNORE_PRE_DEFINED = 0b1; /** * File spec type flag constant, indicating that user-defined file * specifications should not be taken into account. */ - public static final int IGNORE_USER_DEFINED = 0x02; + public static final int IGNORE_USER_DEFINED = 0b10; /** * File spec type constant, indicating a file name specification. */ - public static final int FILE_NAME_SPEC = 0x04; + public static final int FILE_NAME_SPEC = 0b100; /** * File spec type constant, indicating a file extension specification. */ - public static final int FILE_EXTENSION_SPEC = 0x08; + public static final int FILE_EXTENSION_SPEC = 0b1000; + /** + * File spec type constant, indicating a file name pattern specification. + * <code>?</code> represents any single character, <code>*<code> represent any + * string. + * + * @since 3.7 + */ + public static final int FILE_PATTERN_SPEC = 0b10000; /** * Returns a reference to this content type's base type. If this content type diff --git a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentTypeSettings.java b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentTypeSettings.java index a89f67d52..8156e7a1a 100644 --- a/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentTypeSettings.java +++ b/bundles/org.eclipse.core.contenttype/src/org/eclipse/core/runtime/content/IContentTypeSettings.java @@ -28,11 +28,18 @@ public interface IContentTypeSettings { /** * File spec type constant, indicating a file extension specification. */ - public static final int FILE_EXTENSION_SPEC = 0x08; + public static final int FILE_EXTENSION_SPEC = 0b1000; /** * File spec type constant, indicating a file name specification. */ - public static final int FILE_NAME_SPEC = 0x04; + public static final int FILE_NAME_SPEC = 0b100; + + /** + * File spec type constant, indicating a file pattern specification + * + * @since 3.7 + */ + public static final int FILE_PATTERN_SPEC = 0b10000; /** * Adds a user-defined file specification to the corresponding content type. Has no @@ -41,7 +48,8 @@ public interface IContentTypeSettings { * @param fileSpec the file specification * @param type the type of the file specification. One of * <code>FILE_NAME_SPEC</code>, - * <code>FILE_EXTENSION_SPEC</code>. + * <code>FILE_EXTENSION_SPEC</code>, + * <code>FILE_PATTERN_SPEC</code>. * @throws IllegalArgumentException if the type bit mask is * incorrect * @throws CoreException if this method fails. Reasons include: @@ -50,6 +58,7 @@ public interface IContentTypeSettings { * </ul> * @see #FILE_NAME_SPEC * @see #FILE_EXTENSION_SPEC + * @see #FILE_PATTERN_SPEC */ public void addFileSpec(String fileSpec, int type) throws CoreException; |