blob: 1e37af17e187e68373031b0c3b8e8ed31a62bd45 [file] [log] [blame]
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.dltk.core;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.content.IContentTypeManager.IContentTypeChangeListener;
import org.eclipse.dltk.core.environment.EnvironmentPathUtils;
import org.eclipse.dltk.core.environment.IFileHandle;
import org.eclipse.dltk.internal.core.DLTKAssociationManager;
import org.eclipse.dltk.internal.core.NopAssociationManager;
import org.eclipse.dltk.internal.core.ScriptFileConfiguratorManager;
public class DLTKContentTypeManager {
public static final QualifiedName DLTK_VALID = new QualifiedName(
DLTKCore.PLUGIN_ID, "valid"); //$NON-NLS-1$
/**
* Persisted value of the {@link #DLTK_VALID} property
*/
private static final String TRUE_VALUE = "true"; //$NON-NLS-1$
/**
* Persisted value of the {@link #DLTK_VALID} property
*/
private static final String FALSE_VALUE = "false"; //$NON-NLS-1$
private static final boolean DEBUG = false;
private static final boolean DEBUG_CONTENT = false;
private static void log(String message, Object input) {
if (input != null) {
String className = input.getClass().getName();
int pos = className.lastIndexOf('.');
if (pos > 0) {
className = className.substring(pos + 1);
}
System.out.println(message + ' ' + input + ':' + className);
}
}
public static boolean isValidFileNameForContentType(
IDLTKLanguageToolkit toolkit, String name) {
if (DEBUG) {
log(toolkit.getLanguageName(), name);
}
final IDLTKAssociationManager associationManager = getAssociationManager(
toolkit);
if (associationManager.isAssociatedWith(name)) {
return true;
}
// Acquire content types
final IContentType[] contentTypes = getContentTypes(toolkit);
return isValidFileNameForContentType(contentTypes, name);
}
public static boolean isValidFileNameForContentType(
IDLTKLanguageToolkit toolkit, IPath path) {
if (DEBUG) {
log(toolkit.getLanguageName(), path);
}
final IDLTKAssociationManager associationManager = getAssociationManager(
toolkit);
if (associationManager.isAssociatedWith(path.lastSegment())) {
return true;
}
// Acquire content types
final IContentType[] contentTypes = getContentTypes(toolkit);
if (isValidFileNameForContentType(contentTypes, path.lastSegment())) {
return true;
}
if (EnvironmentPathUtils.isFull(path)) {
final IFileHandle file = EnvironmentPathUtils.getFile(path);
if (file != null && file.isFile()) {
if (file.getEnvironment().isLocal()) {
final File localFile = new File(file.toOSString());
return toolkit.canValidateContent(file)
&& validateLocalFileContent(contentTypes,
localFile);
} else {
return toolkit.canValidateContent(file)
&& validateRemoteFileContent(contentTypes, file);
}
}
return false;
}
if (path.isAbsolute()) {
final File file = path.toFile();
if (file.isFile()) {
return toolkit.canValidateContent(file)
&& validateLocalFileContent(contentTypes, file);
}
}
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
final IResource member = root.findMember(path);
return member != null && member.getType() == IResource.FILE
&& validateResourceContent(contentTypes, (IFile) member);
}
private static boolean validateRemoteFileContent(
final IContentType[] derived, IFileHandle file) {
if (DEBUG_CONTENT) {
log("validateContent", file); //$NON-NLS-1$
}
for (int i = 0; i < derived.length; i++) {
IContentType type = derived[i];
InputStream stream = null;
try {
stream = new BufferedInputStream(file.openInputStream(null),
2048);
IContentDescription description = type.getDescriptionFor(stream,
IContentDescription.ALL);
if (description != null) {
if (checkDescription(type, description)) {
return true;
}
}
} catch (IOException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
} finally {
closeStream(stream);
}
}
return false;
}
/**
* @param masterType
* @param derived
* @param file
* @return
*/
private static boolean validateLocalFileContent(IContentType[] derived,
File file) {
if (DEBUG_CONTENT) {
log("validateContent", file); //$NON-NLS-1$
}
for (int i = 0; i < derived.length; i++) {
IContentType type = derived[i];
InputStream stream = null;
try {
stream = new BufferedInputStream(new FileInputStream(file));
IContentDescription description = type.getDescriptionFor(stream,
IContentDescription.ALL);
if (description != null) {
if (checkDescription(type, description)) {
return true;
}
}
} catch (IOException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
} finally {
closeStream(stream);
}
}
return false;
}
private static void closeStream(InputStream stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
}
/**
* Look for derived for associated extensions
*/
private static boolean isValidFileNameForContentType(
IContentType[] contentTypes, String name) {
if (name == null || name.length() == 0) {
return false;
}
for (int i = 0; i < contentTypes.length; i++) {
IContentType type = contentTypes[i];
if (type.isAssociatedWith(name)) {
return true;
}
}
return false;
}
public static boolean isValidResourceForContentType(
IDLTKLanguageToolkit toolkit, IResource resource) {
if (DEBUG) {
log(toolkit.getLanguageName(), resource);
}
if (!(resource instanceof IFile)) {
return false;
}
// Custom filtering via language toolkit
IStatus status = toolkit.validateSourceModule(resource);
if (status.getSeverity() != IStatus.OK) {
return false;
}
final IDLTKAssociationManager associationManager = getAssociationManager(
toolkit);
if (associationManager.isAssociatedWith(resource.getName())) {
try {
configureAsScript((IFile) resource, toolkit.getNatureId());
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
return true;
}
// Acquire content types
final IContentType[] contentTypes = getContentTypes(toolkit);
if (isValidFileNameForContentType(contentTypes, resource.getName())) {
return true;
}
// Delegate the decision if we should validate content to the language
// toolkit.
if (!toolkit.canValidateContent(resource)) {
return false;
}
try {
final String value = resource.getPersistentProperty(DLTK_VALID);
if (value != null) {
return TRUE_VALUE.equals(value);
}
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
return false;
}
final boolean result = validateResourceContent(contentTypes,
(IFile) resource);
setValidScript((IFile) resource, toolkit.getNatureId(), result);
return result;
}
private static boolean validateResourceContent(
final IContentType[] contentTypes, final IFile file) {
if (DEBUG_CONTENT) {
log("validateContent", file); //$NON-NLS-1$
}
try {
if (contentTypes.length != 0 && file.exists()) {
final IContentDescription descr = file.getContentDescription();
if (descr != null) {
if (descr.getContentType().isKindOf(contentTypes[0])) {
return true;
}
}
}
} catch (CoreException e1) {
if (DLTKCore.DEBUG) {
e1.printStackTrace();
}
}
try {
for (int i = 0; i < contentTypes.length; i++) {
final IContentType type = contentTypes[i];
/*
* TODO use something like LazyInputStream if there are multiple
* content types
*/
final InputStream contents = new BufferedInputStream(
file.getContents(), 2048);
try {
final IContentDescription description = type
.getDescriptionFor(contents,
IContentDescription.ALL);
if (description != null) {
if (checkDescription(type, description)) {
return true;
}
}
} catch (IOException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
} finally {
closeStream(contents);
}
}
} catch (CoreException e) {
/*
* CoreException is thrown when resource does not exist, is out of
* sync or something similar - there is no need to process other
* content types if it happens.
*/
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
return false;
}
/**
* Sets value of {@link #DLTK_VALID} property
*
* @param file
* @param natureId
* @param value
*/
private static void setValidScript(IFile file, String natureId,
boolean value) {
try {
file.setPersistentProperty(DLTK_VALID,
value ? TRUE_VALUE : FALSE_VALUE);
if (value) {
configureAsScript(file, natureId);
}
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
private static void configureAsScript(IFile file, String natureId)
throws CoreException {
final IScriptFileConfigurator[] configurators = ScriptFileConfiguratorManager
.get(natureId);
if (configurators != null) {
for (int i = 0; i < configurators.length; ++i) {
configurators[i].configure(file);
}
}
}
private static boolean checkDescription(IContentType type,
IContentDescription description) {
Object object = description.getProperty(DLTK_VALID);
if (object != null && Boolean.TRUE.equals(object)) {
final IContentType contentType = description.getContentType();
return contentType.isKindOf(type);
}
return false;
}
private static final Map<IDLTKLanguageToolkit, IContentType[]> contentTypesCache = new HashMap<>();
private static IContentTypeChangeListener changeListener = null;
/**
* Returns {@link IContentType}s for the specified IDLTKLanguageToolkit. If
* there are no content types found empty array is returned. Master content
* type is returned first.
*
* @param toolkit
* @return
*/
private static IContentType[] getContentTypes(
IDLTKLanguageToolkit toolkit) {
IContentType[] result;
synchronized (contentTypesCache) {
result = contentTypesCache.get(toolkit);
}
if (result != null) {
return result;
}
final IContentTypeManager manager = Platform.getContentTypeManager();
synchronized (contentTypesCache) {
if (changeListener == null) {
changeListener = event -> {
synchronized (contentTypesCache) {
contentTypesCache.clear();
}
};
manager.addContentTypeChangeListener(changeListener);
}
}
final IContentType masterType = manager
.getContentType(toolkit.getLanguageContentType());
if (masterType != null) {
final Set<IContentType> selected = new HashSet<>();
for (IContentType type : manager.getAllContentTypes()) {
if (type.isKindOf(masterType)) {
selected.add(type);
}
}
result = selected.toArray(new IContentType[selected.size()]);
for (int i = 1; i < result.length; ++i) {
if (result[i] == masterType) {
final IContentType temp = result[0];
result[0] = result[i];
result[i] = temp;
break;
}
}
} else {
result = new IContentType[0];
}
synchronized (contentTypesCache) {
contentTypesCache.put(toolkit, result);
}
return result;
}
private static final Map<IDLTKLanguageToolkit, IDLTKAssociationManager> associationManagerCache = new HashMap<>();
/**
* @param toolkit
* @return
*/
private static IDLTKAssociationManager getAssociationManager(
IDLTKLanguageToolkit toolkit) {
IDLTKAssociationManager manager;
synchronized (associationManagerCache) {
manager = associationManagerCache.get(toolkit);
}
if (manager != null) {
return manager;
}
manager = toolkit.getPreferenceQualifier() != null
? new DLTKAssociationManager(toolkit.getNatureId(),
toolkit.getPreferenceQualifier())
: new NopAssociationManager();
synchronized (associationManagerCache) {
associationManagerCache.put(toolkit, manager);
}
return manager;
}
private static IResourceChangeListener listener = null;
private static class ResetScriptValidPropertyListener
implements IResourceChangeListener, IResourceDeltaVisitor {
@Override
public void resourceChanged(IResourceChangeEvent event) {
try {
event.getDelta().accept(this);
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
final IResource resource = delta.getResource();
if (resource.getType() == IResource.FILE) {
if (delta.getKind() == IResourceDelta.CHANGED
&& (delta.getFlags() & (IResourceDelta.CONTENT
| IResourceDelta.REPLACED)) != 0) {
resource.setPersistentProperty(DLTK_VALID, null);
}
return false;
}
return true;
}
}
public static void installListener() {
if (listener == null) {
listener = new ResetScriptValidPropertyListener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(listener,
IResourceChangeEvent.POST_CHANGE);
}
}
public static void uninstallListener() {
if (listener != null) {
ResourcesPlugin.getWorkspace()
.removeResourceChangeListener(listener);
listener = null;
}
}
}