blob: 8f00c3010ea776140aeeb5f49e2f7e53945a92d2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.content;
import java.io.*;
import java.util.*;
import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.content.*;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.service.prefs.Preferences;
public class ContentTypeManager implements IContentTypeManager {
public final static String CONTENT_TYPE_PREF_NODE = Platform.PI_RUNTIME + IPath.SEPARATOR + "content-types"; //$NON-NLS-1$
private static final String OPTION_DEBUG_CONTENT_TYPES = Platform.PI_RUNTIME + "/contenttypes/debug"; //$NON-NLS-1$;
static final boolean DEBUGGING = Boolean.TRUE.toString().equalsIgnoreCase(InternalPlatform.getDefault().getOption(OPTION_DEBUG_CONTENT_TYPES));
private static ContentTypeManager instance;
public static final int MARK_LIMIT = 0x400;
private ContentTypeBuilder builder;
private Map catalog = new HashMap();
// a comparator used when resolving conflicts (two types associated to the same spec)
private Comparator conflictComparator = new Comparator() {
public int compare(Object o1, Object o2) {
ContentType type1 = (ContentType) o1;
ContentType type2 = (ContentType) o2;
// first criteria: depth - the lower, the better
int depthCriteria = type1.getDepth() - type2.getDepth();
if (depthCriteria != 0)
return depthCriteria;
// second criteria: priority - the higher, the better
int priorityCriteria = type1.getPriority() - type2.getPriority();
if (priorityCriteria != 0)
return -priorityCriteria;
// to ensure stability
return type1.getId().compareTo(type2.getId());
}
};
private Comparator depthComparator = new Comparator() {
public int compare(Object o1, Object o2) {
return ((ContentType) o2).getDepth() - ((ContentType) o1).getDepth();
}
};
private Map fileExtensions = new HashMap();
private Map fileNames = new HashMap();
/*
* Returns the extension for a file name (omiting the leading '.').
*/
static String getFileExtension(String fileName) {
int dotPosition = fileName.lastIndexOf('.');
return (dotPosition == -1 || dotPosition == fileName.length() - 1) ? null : fileName.substring(dotPosition + 1);
}
public synchronized static ContentTypeManager getInstance() {
if (instance != null)
return instance;
instance = new ContentTypeManager();
instance.startup();
return instance;
}
/*
* Returns null if no bytes can be read
*/
protected static LazyInputStream readBuffer(InputStream contents) {
return new LazyInputStream(contents, MARK_LIMIT);
}
protected static LazyReader readBuffer(Reader contents) {
return new LazyReader(contents, MARK_LIMIT);
}
/**
* Constructs a new content type manager.
*/
protected ContentTypeManager() {
// just to set visibility
}
protected void addContentType(IContentType contentType) {
catalog.put(contentType.getId(), contentType);
}
private void addFileSpecContributor(IContentType contentType, int fileSpecType, Map fileSpecsMap) {
String[] fileSpecs = contentType.getFileSpecs(fileSpecType);
for (int i = 0; i < fileSpecs.length; i++) {
String mappingKey = FileSpec.getMappingKeyFor(fileSpecs[i]);
Set existing = (Set) fileSpecsMap.get(mappingKey);
if (existing == null)
fileSpecsMap.put(mappingKey, existing = new TreeSet(conflictComparator));
existing.add(contentType);
}
}
protected ContentTypeBuilder createBuilder() {
return new ContentTypeBuilder(this);
}
/**
* A content type will be valid if:
* <ol>
* <li>it does not designate a base type, or</li>
* <li>it designates a base type that exists and is valid</li>
* </ol>
*/
private boolean ensureValid(ContentType type) {
if (type.getValidation() != ContentType.STATUS_UNKNOWN)
return type.isValid();
if (type.getBaseTypeId() == null) {
type.setValidation(ContentType.STATUS_VALID);
return true;
}
ContentType baseType = (ContentType) catalog.get(type.getBaseTypeId());
if (baseType == null) {
type.setValidation(ContentType.STATUS_INVALID);
return false;
}
// set this type temporarily as invalid to prevent cycles
// all types in a cycle would stay as invalid
type.setValidation(ContentType.STATUS_INVALID);
ensureValid(baseType);
// base type is either valid or invalid - type will have the same status
type.setValidation(baseType.getValidation());
return type.isValid();
}
/**
* @see IContentTypeManager
*/
public IContentType findContentTypeFor(InputStream contents, String fileName) throws IOException {
IContentType[] all = findContentTypesFor(contents, fileName);
return all.length > 0 ? all[0] : null;
}
/**
* @see IContentTypeManager
*/
public IContentType findContentTypeFor(String fileName) {
// basic implementation just gets all content types
IContentType[] associated = findContentTypesFor(fileName);
return associated.length == 0 ? null : associated[0];
}
/**
* @see IContentTypeManager
*/
public IContentType[] findContentTypesFor(InputStream contents, String fileName) throws IOException {
IContentType[] subset = fileName != null ? findContentTypesFor(fileName) : getAllContentTypes();
InputStream buffer = readBuffer(contents);
return internalFindContentTypesFor(buffer, subset);
}
/**
* @see IContentTypeManager
*/
public IContentType[] findContentTypesFor(String fileName) {
List result = new ArrayList(5);
int count = 0;
// files associated by name should appear before those associated by extension
SortedSet allByFileName = (SortedSet) fileNames.get(FileSpec.getMappingKeyFor(fileName));
if (allByFileName != null && !allByFileName.isEmpty()) {
ContentType main = ((ContentType) allByFileName.first()).getTarget();
result.add(count++, main);
IContentType[] children = main.getChildren();
for (int i = 0; i < children.length; i++) {
ContentType child = (ContentType) children[i];
// must avoid duplicates and ensure children do not override filespecs
if (!result.contains(child) && child.internalIsAssociatedWith(fileName) == ContentType.ASSOCIATED_BY_NAME)
result.add(count++, child);
}
}
String fileExtension = getFileExtension(fileName);
if (fileExtension != null) {
SortedSet allByFileExtension = (SortedSet) fileExtensions.get(FileSpec.getMappingKeyFor(fileExtension));
if (allByFileExtension != null && !allByFileExtension.isEmpty()) {
ContentType main = ((ContentType) allByFileExtension.first()).getTarget();
if (!result.contains(main)) {
result.add(count++, main);
IContentType[] children = main.getChildren();
for (int i = 0; i < children.length; i++) {
ContentType child = (ContentType) children[i];
// must avoid duplicates and ensure children do not override filespecs
if (!result.contains(children[i]) && child.internalIsAssociatedWith(fileName) == ContentType.ASSOCIATED_BY_EXTENSION)
result.add(count++, children[i]);
}
}
}
}
return (IContentType[]) result.toArray(new IContentType[result.size()]);
}
/**
* @see IContentTypeManager
*/
public IContentType[] getAllContentTypes() {
List result = new ArrayList(catalog.size());
for (Iterator i = catalog.values().iterator(); i.hasNext();) {
ContentType type = (ContentType) i.next();
if (type.isValid() && !type.isAlias())
result.add(type);
}
return (IContentType[]) result.toArray(new IContentType[result.size()]);
}
//TODO need better performance here
public IContentType[] getChildren(IContentType parent) {
List result = new ArrayList(5);
for (Iterator i = this.catalog.values().iterator(); i.hasNext();) {
IContentType next = (IContentType) i.next();
if (next != parent && next.isKindOf(parent))
result.add(next);
}
return (IContentType[]) result.toArray(new IContentType[result.size()]);
}
/**
* @see IContentTypeManager
*/
public IContentType getContentType(String contentTypeIdentifier) {
ContentType type = internalGetContentType(contentTypeIdentifier);
return (type != null && type.isValid() && !type.isAlias()) ? type : null;
}
/**
* @see IContentTypeManager
*/
public IContentDescription getDescriptionFor(InputStream contents, String fileName, QualifiedName[] options) throws IOException {
// naive implementation for now
InputStream buffer = readBuffer(contents);
IContentType[] subset = fileName != null ? findContentTypesFor(fileName) : getAllContentTypes();
IContentType[] selected = internalFindContentTypesFor(buffer, subset);
if (selected.length == 0)
return null;
return ((ContentType) selected[0]).internalGetDescriptionFor(buffer, options);
}
/**
* @see IContentTypeManager
*/
public IContentDescription getDescriptionFor(Reader contents, String fileName, QualifiedName[] options) throws IOException {
Reader buffer = readBuffer(contents);
IContentType[] subset = fileName != null ? findContentTypesFor(fileName) : getAllContentTypes();
IContentType[] selected = internalFindContentTypesFor(buffer, subset);
if (selected.length == 0)
return null;
return ((ContentType) selected[0]).internalGetDescriptionFor(buffer, options);
}
Preferences getPreferences() {
return new InstanceScope().getNode(CONTENT_TYPE_PREF_NODE);
}
protected IContentType[] internalFindContentTypesFor(InputStream buffer, IContentType[] subset) {
if (buffer == null) {
Arrays.sort(subset, depthComparator);
return subset;
}
List appropriate = new ArrayList();
int valid = 0;
for (int i = 0; i < subset.length; i++) {
ContentType current = (ContentType) subset[i];
IContentDescriber describer = current.getDescriber();
int status = IContentDescriber.INDETERMINATE;
if (describer != null) {
status = current.describe(describer, buffer, null);
if (status == IContentDescriber.INVALID)
continue;
}
if (status == IContentDescriber.VALID)
appropriate.add(valid++, current);
else
appropriate.add(current);
}
IContentType[] result = (IContentType[]) appropriate.toArray(new IContentType[appropriate.size()]);
if (valid > 1)
Arrays.sort(result, 0, valid, depthComparator);
if (result.length - valid > 1)
Arrays.sort(result, valid, result.length, depthComparator);
return result;
}
private IContentType[] internalFindContentTypesFor(Reader buffer, IContentType[] subset) {
if (buffer == null) {
Arrays.sort(subset, depthComparator);
return subset;
}
List appropriate = new ArrayList();
int valid = 0;
for (int i = 0; i < subset.length; i++) {
ContentType current = (ContentType) subset[i];
IContentDescriber describer = current.getDescriber();
int status = IContentDescriber.INDETERMINATE;
if (describer instanceof ITextContentDescriber) {
status = current.describe((ITextContentDescriber) describer, buffer, null);
if (status == IContentDescriber.INVALID)
continue;
}
if (status == IContentDescriber.VALID)
appropriate.add(valid++, current);
else
appropriate.add(current);
}
IContentType[] result = (IContentType[]) appropriate.toArray(new IContentType[appropriate.size()]);
if (valid > 1)
Arrays.sort(result, 0, valid, depthComparator);
if (result.length - valid > 1)
Arrays.sort(result, valid, result.length, depthComparator);
return result;
}
ContentType internalGetContentType(String contentTypeIdentifier) {
return (ContentType) catalog.get(contentTypeIdentifier);
}
private void makeAliases(Map fileSpecs) {
// process all content types per file specs
// marking conflicting content types as aliases
// for the higher priority content type
for (Iterator i = fileSpecs.values().iterator(); i.hasNext();) {
Set associated = (Set) i.next();
if (associated.size() < 2)
// no conflicts here
continue;
Iterator j = associated.iterator();
ContentType ellected = (ContentType) j.next();
while (j.hasNext())
((ContentType) j.next()).setAliasTarget(ellected);
}
}
protected void reorganize() {
fileExtensions.clear();
fileNames.clear();
// forget the validation status and aliases for all content types
for (Iterator i = catalog.values().iterator(); i.hasNext();) {
ContentType type = ((ContentType) i.next());
type.setValidation(ContentType.STATUS_UNKNOWN);
type.setAliasTarget(null);
}
// do the validation
for (Iterator i = catalog.values().iterator(); i.hasNext();) {
ContentType type = (ContentType) i.next();
if (!ensureValid(type))
continue;
addFileSpecContributor(type, IContentType.FILE_EXTENSION_SPEC, fileExtensions);
addFileSpecContributor(type, IContentType.FILE_NAME_SPEC, fileNames);
}
makeAliases(fileNames);
makeAliases(fileExtensions);
}
protected void startup() {
builder = createBuilder();
catalog = new HashMap();
builder.startup();
builder.buildContentTypes();
}
/* (non-Javadoc)
* @see IContentTypeManager#addContentTypeChangeListener(IContentTypeChangeListener)
*/
public void addContentTypeChangeListener(IContentTypeChangeListener listener) {
// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=67884
// Content type change events not reported
}
/* (non-Javadoc)
* @see IContentTypeManager#removeContentTypeChangeListener(IContentTypeChangeListener)
*/
public void removeContentTypeChangeListener(IContentTypeChangeListener listener) {
// TODO https://bugs.eclipse.org/bugs/show_bug.cgi?id=67884
// Content type change events not reported
}
}