blob: d9e81f57bde8170892c120bc523270a63fba173b [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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/org/documents/epl-v10.php
*
* 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.android.ide.eclipse.adt.internal.editors.layout;
import com.android.ide.eclipse.adt.AndroidConstants;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFileListener;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IResourceEventListener;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Monitor for file changes triggering a layout redraw.
*/
public final class LayoutReloadMonitor implements IFileListener, IResourceEventListener {
// singleton, enforced by private constructor.
private final static LayoutReloadMonitor sThis = new LayoutReloadMonitor();
/**
* Map of listeners by IProject.
*/
private final Map<IProject, List<ILayoutReloadListener>> mListenerMap =
new HashMap<IProject, List<ILayoutReloadListener>>();
private final static int CHANGE_CODE = 0;
private final static int CHANGE_RESOURCES = 1;
private final static int CHANGE_R = 2;
private final static int CHANGE_COUNT = 3;
/**
* List of projects having received a file change. the boolean[] contains 3 values:
* <ul><li>CHANGE_CODE: code change flag.</li>
* <li>CHANGE_RESOURCES: resource change flag.</li>
* <li>CHANGE_R: R clas change flag</li></ul>
*/
private final Map<IProject, boolean[]> mChangedProjects = new HashMap<IProject, boolean[]>();
/**
* Classes which implement this interface provide a method to respond to resource changes
* triggering a layout redraw
*/
public interface ILayoutReloadListener {
/**
* Sent when the layout needs to be redrawn
* @param codeChange The trigger happened due to a code change.
* @param rChange The trigger happened due to a change in the R class.
* @param resChange The trigger happened due to a resource change.
*/
void reloadLayout(boolean codeChange, boolean rChange, boolean resChange);
}
/**
* Returns the single instance of {@link LayoutReloadMonitor}.
*/
public static LayoutReloadMonitor getMonitor() {
return sThis;
}
private LayoutReloadMonitor() {
ResourceMonitor monitor = ResourceMonitor.getMonitor();
monitor.addFileListener(this, IResourceDelta.ADDED | IResourceDelta.CHANGED);
monitor.addResourceEventListener(this);
}
/**
* Adds a listener for a given {@link IProject}.
* @param project
* @param listener
*/
public void addListener(IProject project, ILayoutReloadListener listener) {
synchronized (mListenerMap) {
List<ILayoutReloadListener> list = mListenerMap.get(project);
if (list == null) {
list = new ArrayList<ILayoutReloadListener>();
mListenerMap.put(project, list);
}
list.add(listener);
}
}
/**
* Removes a listener for a given {@link IProject}.
* @param project
* @param listener
*/
public void removeListener(IProject project, ILayoutReloadListener listener) {
synchronized (mListenerMap) {
List<ILayoutReloadListener> list = mListenerMap.get(project);
if (list != null) {
list.remove(listener);
}
}
}
/*
* (non-Javadoc)
* @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener#fileChanged(org.eclipse.core.resources.IFile, org.eclipse.core.resources.IMarkerDelta[], int)
*
* Callback for ResourceMonitor.IFileListener. Called when a file changed.
* This records the changes for each project, but does not notify listeners.
* @see #resourceChangeEventEnd
*/
public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
// get the file project
IProject project = file.getProject();
// if this project has already been marked as modified, we do nothing.
boolean[] changeFlags = mChangedProjects.get(project);
if (changeFlags != null && changeFlags[CHANGE_CODE] && changeFlags[CHANGE_RESOURCES] &&
changeFlags[CHANGE_R]) {
return;
}
// now check that the file is *NOT* a layout file (those automatically trigger a layout
// reload and we don't want to do it twice.
ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
if (resFolder != null) {
if (resFolder.getType() != ResourceFolderType.LAYOUT) {
// this is a resource change!
if (changeFlags == null) {
changeFlags = new boolean[CHANGE_COUNT];
mChangedProjects.put(project, changeFlags);
}
changeFlags[CHANGE_RESOURCES] = true;
}
} else if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) {
if (file.getName().matches("R[\\$\\.](.*)")) {
// this is a R change!
if (changeFlags == null) {
changeFlags = new boolean[CHANGE_COUNT];
mChangedProjects.put(project, changeFlags);
}
changeFlags[CHANGE_R] = true;
} else {
// this is a code change!
if (changeFlags == null) {
changeFlags = new boolean[CHANGE_COUNT];
mChangedProjects.put(project, changeFlags);
}
changeFlags[CHANGE_CODE] = true;
}
}
}
/*
* (non-Javadoc)
* @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventStart()
*
* Callback for ResourceMonitor.IResourceEventListener. Called at the beginning of a resource
* change event. This is called once, while fileChanged can be called several times.
*
*/
public void resourceChangeEventStart() {
// nothing to be done here, it all happens in the resourceChangeEventEnd
}
/*
* (non-Javadoc)
* @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventEnd()
*
* Callback for ResourceMonitor.IResourceEventListener. Called at the end of a resource
* change event. This is where we notify the listeners.
*/
public void resourceChangeEventEnd() {
// for each IProject that was changed, we notify all the listeners.
synchronized (mListenerMap) {
for (Entry<IProject, boolean[]> project : mChangedProjects.entrySet()) {
List<ILayoutReloadListener> listeners = mListenerMap.get(project.getKey());
boolean[] flags = project.getValue();
if (listeners != null) {
for (ILayoutReloadListener listener : listeners) {
listener.reloadLayout(flags[CHANGE_CODE], flags[CHANGE_R],
flags[CHANGE_RESOURCES]);
}
}
}
}
// empty the list.
mChangedProjects.clear();
}
}