/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.core.internal.model;
import com.google.dart.server.utilities.general.StringUtilities;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.model.DartIgnoreEvent;
import com.google.dart.tools.core.model.DartIgnoreListener;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ListenerList;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Pattern;
/**
* The unique instance of the class <code>DartIgnoreManager</code> is used to manage the ignored
* elements in the Dart element model.
*
* @coverage dart.tools.core.model
*/
public class DartIgnoreManager {
private static final String IGNORE_FILE_NAME = ".dartignore";
private static final DartIgnoreManager INSTANCE = new DartIgnoreManager();
public static final String[] DEFAULT_IGNORE_REGEX = new String[] {
// Ignore dart2js generated files
".*\\.js\\.info\\.html" //
};
private static final Pattern[] DEFAULT_IGNORE_PATTERNS = new Pattern[DEFAULT_IGNORE_REGEX.length];
static {
for (int i = 0; i < DEFAULT_IGNORE_PATTERNS.length; i++) {
DEFAULT_IGNORE_PATTERNS[i] = Pattern.compile(DEFAULT_IGNORE_REGEX[i]);
}
}
public static final DartIgnoreManager getInstance() {
return INSTANCE;
}
/**
* Return {@code true} if the absolute path for the given resource is included in the default
* collection of paths to be ignored.
*
* @param resource the resource
* @return {@code true} if the resource is ignored by default
*/
public static boolean isIgnoredByDefault(IResource resource) {
return isIgnoredByDefault(getPathPattern(resource));
}
/**
* Return {@code true} if a path is included in the default collection of paths to be ignored.
*
* @param absolutePath the platform independent absolute path being tested. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return <code>true</code> if the given path should be ignored
*/
public static boolean isIgnoredByDefault(String absolutePath) {
if (absolutePath != null) {
for (Pattern pattern : DEFAULT_IGNORE_PATTERNS) {
if (pattern.matcher(absolutePath).matches()) {
return true;
}
}
}
return false;
}
private static String getPathPattern(File file) {
return file != null ? file.getAbsolutePath().replace(File.separatorChar, '/') : null;
}
private static String getPathPattern(IPath location) {
return location != null ? location.toPortableString() : null;
}
private static String getPathPattern(IResource resource) {
return resource != null ? getPathPattern(resource.getLocation()) : null;
}
/**
* Stores the patterns indicating which elements should be ignored (not {@code null}). Call
* {@link #loadContent()} before accessing this field.
*/
private final DartIgnoreFile storage;
/**
* Objects to be notified when the exclusion list changes.
*/
private final ListenerList listeners = new ListenerList();
/**
* A list of exclusion patterns that are to be applied to determine which files are not currently
* being analyzed, or <code>null</code> if the patterns have not yet been read from disk.
*/
private ArrayList<String> exclusionPatterns;
public DartIgnoreManager() {
this(new DartIgnoreFile(ResourcesPlugin.getWorkspace().getRoot().getLocation().append(
IGNORE_FILE_NAME).toFile()));
}
public DartIgnoreManager(DartIgnoreFile storage) {
this.storage = storage;
}
/**
* Add the given listener for dart ignore changes to the Dart Model. Has no effect if an identical
* listener is already registered.
*
* @param listener the listener to add
*/
public void addListener(DartIgnoreListener listener) {
listeners.add(listener);
}
/**
* Add the specified file to the list of ignores. Callers are responsible for deleting any
* existing markers on ignored resources.
*
* @param file the file to ignore
* @return {@code true} if the list of ignores has changed
*/
public boolean addToIgnores(File file) throws IOException {
return addToIgnores(getPathPattern(file));
}
/**
* Add the specified path to the list of ignores. Callers are responsible for deleting any
* existing markers on ignored resources.
*
* @param absolutePath the absolute path to ignore
* @return {@code true} if the list of ignores has changed
*/
public boolean addToIgnores(IPath absolutePath) throws IOException {
return addToIgnores(getPathPattern(absolutePath));
}
/**
* Add the path for the given resource to the list of ignores. Existing Dart problem markers on
* the specified resource will be removed.
*
* @param resource the resource to ignore
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
* @throws CoreException if there was an error deleting markers
*/
public boolean addToIgnores(IResource resource) throws IOException, CoreException {
return addToIgnores(new IResource[] {resource});
}
/**
* Add the paths for the given resources to the list of ignores. Existing Dart problem markers on
* the specified resources will be removed.
*
* @param resources the resources to ignore
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
* @throws CoreException if there was an error deleting markers
*/
public boolean addToIgnores(IResource[] resources) throws IOException, CoreException {
String[] paths = new String[resources.length];
for (int index = 0; index < paths.length; index++) {
final IResource resource = resources[index];
paths[index] = getPathPattern(resource);
// TODO (danrubel): Move delete markers into ProjectManager listener
if (resource != null) {
resource.deleteMarkers(DartCore.DART_PROBLEM_MARKER_TYPE, true, IResource.DEPTH_INFINITE);
}
}
return addToIgnores(paths);
}
/**
* Add the specified path to the list of ignores. Callers are responsible for deleting any
* existing markers on ignored resources.
*
* @param absolutePath the platform independent absolute path being tested. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return {@code true} if the list of ignores has changed
*/
public boolean addToIgnores(String absolutePath) throws IOException {
return addToIgnores(new String[] {absolutePath});
}
/**
* Add the specified paths to the list of ignores. Callers are responsible for deleting any
* existing markers on ignored resources.
*
* @param absolutePaths the platform independent absolute paths being tested. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return {@code true} if the list of ignores has changed
*/
public boolean addToIgnores(String[] absolutePaths) throws IOException {
if (absolutePaths != null) {
boolean modified = false;
for (String absolutePath : absolutePaths) {
if (absolutePath != null) {
loadContent();
if (storage.add(absolutePath)) {
modified = true;
}
}
}
if (modified) {
cacheExclusions();
storage.store();
notifyListeners(new DartIgnoreEvent(absolutePaths, StringUtilities.EMPTY_ARRAY));
return true;
}
}
return false;
}
/**
* Return a list of exclusion patterns that are to be applied to determine which files are not
* currently being analyzed.
*
* @return the exclusion patterns used to determine which files are not currently being analyzed
*/
public ArrayList<String> getExclusionPatterns() {
loadContent();
return exclusionPatterns;
}
/**
* Determine if the specified file should be analyzed. This means that the file is not
* {@code null}, the file exists, and the file's location is not included in the collection of
* paths to be ignored.
*
* @param file the file
* @return {@code true} if the file should be analyzed
*/
public boolean isAnalyzed(File file) {
return file != null && file.exists() && isAnalyzed(getPathPattern(file));
}
/**
* Determine if the file or resource with the specified path should be analyzed. This means that
* the specified path is not {@code null} and is not included in the collection of paths to be
* ignored.
*
* @param absolutePath the platform independent absolute path being tested. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return {@code true} if the file or resource represented by this path should be analyzed
*/
public boolean isAnalyzed(IPath absolutePath) {
return absolutePath != null && !isIgnored(absolutePath);
}
/**
* Determine if the specified resource should be analyzed. This means that the resource is not
* {@code null}, the resource location is not {@code null}, the resource exists, and the
* resource's location is not included in the collection of paths to be ignored.
*
* @param resource the resource
* @return {@code true} if the resource should be analyzed
*/
public boolean isAnalyzed(IResource resource) {
return resource != null && resource.exists() && isAnalyzed(getPathPattern(resource));
}
/**
* Determine if the file or resource with the specified path should be analyzed. This means that
* the specified path is not {@code null} and is not included in the collection of paths to be
* ignored.
*
* @param absolutePath the platform independent absolute path being tested. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return {@code true} if the file or resource represented by this path should be analyzed
*/
public boolean isAnalyzed(String absolutePath) {
return absolutePath != null && !isIgnored(absolutePath);
}
/**
* Return <code>true</code> if the specified file's path is included in the collection of paths to
* be ignored.
*
* @param file the file being tested
* @return <code>true</code> if the given file should be ignored
*/
public boolean isIgnored(File file) {
return isIgnored(getPathPattern(file));
}
/**
* Return <code>true</code> if the specified path is included in the collection of paths to be
* ignored.
*
* @param path the file being tested
* @return <code>true</code> if the given file should be ignored
*/
public boolean isIgnored(IPath path) {
return isIgnored(getPathPattern(path));
}
/**
* Return <code>true</code> if the specified resource's path is included in the collection of
* paths to be ignored.
*
* @param resource the resource being tested
* @return <code>true</code> if the given resource should be ignored
*/
public boolean isIgnored(IResource resource) {
return isIgnored(getPathPattern(resource));
}
/**
* Return <code>true</code> if the path is included in the collection of paths to be ignored.
*
* @param absolutePath the platform independent absolute path being tested. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return <code>true</code> if the given path should be ignored
*/
public boolean isIgnored(String absolutePath) {
if (absolutePath != null) {
// TODO(brianwilkerson) Re-implement this once the real semantics have been decided on.
ArrayList<String> patterns = getExclusionPatterns();
if (patterns.size() > 0) {
for (String pattern : patterns) {
// TODO(brianwilkerson) Replace this with some form of pattern matching.
if (absolutePath.equals(pattern) || absolutePath.startsWith(pattern + "/")) {
return true;
}
}
}
return isIgnoredByDefault(absolutePath);
}
return false;
}
/**
* Remove the file's path for the given resource from the list of ignores.
*
* @param file the file to (un)ignore
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
*/
public boolean removeFromIgnores(File file) throws IOException {
return removeFromIgnores(getPathPattern(file));
}
/**
* Remove the path from the list of ignores.
*
* @param absolutePath the absolute path being removed.
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
*/
public boolean removeFromIgnores(IPath absolutePath) throws IOException {
return removeFromIgnores(getPathPattern(absolutePath));
}
/**
* Remove the path for the given resource from the list of ignores.
*
* @param resource the resource to (un)ignore
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
*/
public boolean removeFromIgnores(IResource resource) throws IOException {
return removeFromIgnores(new IResource[] {resource});
}
/**
* Remove the paths for the given resources from the list of ignores.
*
* @param resources the resources to (un)ignore
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
*/
public boolean removeFromIgnores(IResource[] resources) throws IOException {
String[] paths = new String[resources.length];
for (int index = 0; index < paths.length; index++) {
paths[index] = getPathPattern(resources[index]);
}
return removeFromIgnores(paths);
}
/**
* Remove the path from the list of ignores.
*
* @param absolutePath the platform independent absolute path being removed. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
*/
public boolean removeFromIgnores(String absolutePath) throws IOException {
return removeFromIgnores(new String[] {absolutePath});
}
/**
* Remove the paths from the list of ignores.
*
* @param absolutePaths the platform independent absolute paths being removed. On Windows, any '\'
* must be converted to '/' before calling this method.
* @return {@code true} if the list of ignores has changed
* @throws IOException if there was an error accessing the ignore file
*/
public boolean removeFromIgnores(String[] absolutePaths) throws IOException {
if (absolutePaths != null) {
Collection<String> removed = new ArrayList<String>();
for (String absolutePath : absolutePaths) {
if (absolutePath != null) {
loadContent();
removed.addAll(storage.remove(absolutePath));
}
}
if (removed.size() > 0) {
cacheExclusions();
storage.store();
notifyListeners(new DartIgnoreEvent(StringUtilities.EMPTY_ARRAY, absolutePaths));
return true;
}
}
return false;
}
/**
* Remove the given listener for dart ignore changes from the Dart Model. Has no effect if an
* identical listener is not registered.
*
* @param listener the non-<code>null</code> listener to remove
*/
public void removeListener(DartIgnoreListener listener) {
listeners.remove(listener);
}
/**
* If the underlying storage has changed, recache the {@link #exclusionPatterns}.
*/
private void cacheExclusions() {
if (exclusionPatterns == null) {
exclusionPatterns = new ArrayList<String>();
} else {
exclusionPatterns.clear();
}
exclusionPatterns.addAll(storage.getPatterns());
}
/**
* Initialize the receiver's content if not already initialized.
*/
private void loadContent() {
// TODO(brianwilkerson) Re-implement this once the real semantics have been decided on.
if (exclusionPatterns == null) {
exclusionPatterns = new ArrayList<String>();
try {
storage.initFile();
storage.load();
cacheExclusions();
} catch (IOException exception) {
DartCore.logInformation("Could not read ignore file from workspace", exception);
}
}
}
/**
* Notify listeners that the exclusion patterns have changed.
*/
private void notifyListeners(DartIgnoreEvent event) {
for (Object listener : listeners.getListeners()) {
((DartIgnoreListener) listener).ignoresChanged(event);
}
}
}