/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.editors.profiles;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.runtime.Preferences;
import com.aptana.ide.core.FileUtils;
import com.aptana.ide.core.IdeLog;
import com.aptana.ide.core.StringUtils;
import com.aptana.ide.core.Trace;
import com.aptana.ide.core.ui.CoreUIUtils;
import com.aptana.ide.editors.UnifiedEditorsPlugin;
import com.aptana.ide.editors.managers.FileContextManager;
import com.aptana.ide.editors.unified.DocumentSourceProvider;
import com.aptana.ide.editors.unified.FileService;
import com.aptana.ide.editors.unified.FileSourceProvider;
import com.aptana.ide.editors.unified.IFileServiceFactory;
import com.aptana.ide.editors.unified.ILanguageEnvironment;
import com.aptana.ide.editors.unified.utils.IUpdaterThreadUpdateable;
import com.aptana.ide.editors.unified.utils.UpdaterThread;
import com.aptana.ide.parsing.IParseState;
/**
* ProfileManager
*/
public class ProfileManager implements IUpdaterThreadUpdateable
{
/**
* The delay before we trigger any apply profile delays
*/
public static int APPLY_PROFILE_DELAY = 1000;
/**
* DEFAULT_PROFILE_NAME
*/
public static final String DEFAULT_PROFILE_NAME = "Default Profile"; //$NON-NLS-1$
/**
* staticProtocol
*/
static final String staticProtocol = "static://"; //$NON-NLS-1$
/**
* titleLabel
*/
static final String titleLabel = " (Auto-created)"; //$NON-NLS-1$
/**
* DEFAULT_PROFILE_PATH
*/
public static final String DEFAULT_PROFILE_PATH = staticProtocol + DEFAULT_PROFILE_NAME;
Profile currentProfile = null;
ArrayList listeners = new ArrayList();
ArrayList appliedListeners = new ArrayList();
HashMap profiles = new HashMap();
private HashMap languageEnvironments = new HashMap();
private HashMap languageFactories = new HashMap();
// private String _activeFilePath = StringUtils.EMPTY;
// private HashMap indexToPath = new HashMap();
// private HashMap indexToContext = new HashMap();
private UpdaterThread _applyProfilesThread = null;
private HashMap changeListenerHash = new HashMap();
/**
* ProfileManager
*/
public ProfileManager()
{
this(true);
}
/**
* ProfileManager
*
* @param threaded
*/
public ProfileManager(boolean threaded)
{
if (threaded)
{
startProfileThread();
}
loadStaticProfiles();
}
private void startProfileThread()
{
_applyProfilesThread = new UpdaterThread(this, APPLY_PROFILE_DELAY, Messages.ProfileManager_ApplyProfiles);
_applyProfilesThread.start();
}
private void loadStaticProfiles()
{
boolean defaultCreated = false;
try
{
Preferences prefs = UnifiedEditorsPlugin.getDefault().getPluginPreferences();
String profilesList = prefs.getString(Profile.getProfileListKey());
if (profilesList.length() == 0)
{
return;
}
String[] list = profilesList.split(","); //$NON-NLS-1$
for (int i = 0; i < list.length; i++)
{
if (list[i].length() > 0)
{
Trace.info(Messages.ProfileManager_LoadingProfile + list[i]);
String[] parts = list[i].split("="); //$NON-NLS-1$
String name = parts[0];
String path = parts[1];
createProfile(name, path);
if (list[i].equals(DEFAULT_PROFILE_NAME))
{
defaultCreated = true;
}
}
}
}
catch (Exception ex)
{
// try/catch check for use in UnitTests (which may not have the
// plugin loaded)
}
finally
{
if (!defaultCreated)
{
createProfile(DEFAULT_PROFILE_NAME, DEFAULT_PROFILE_PATH);
}
this.setCurrentProfile(DEFAULT_PROFILE_PATH);
}
}
/**
* createProfile
*
* @param name
* @param path
* @return Profile
*/
public Profile createProfile(String name, String path)
{
return createProfile(name, path, false);
}
/**
* createProfile
*
* @param name
* @param path
* @param dynamic
* @return Profile
*/
public Profile createProfile(String name, String path, boolean dynamic)
{
Profile profile = new Profile(name, path, dynamic);
addProfile(profile);
return profile;
}
/**
* Adds a profile to the list of profiles
*
* @param profile
*/
public void addProfile(Profile profile)
{
IProfileChangeListener pcl = new IProfileChangeListener()
{
public void onProfileChanged(Profile p)
{
fireProfileChangeEvent(p);
applyProfiles();
}
};
changeListenerHash.put(profile.getURI(), pcl);
profile.addProfileChangeListener(pcl);
profiles.put(profile.getURI(), profile);
fireProfileChangeEvent(profile);
}
/**
* Removes a profile from the list of profiles
*
* @param path
* The path of the profile to remove
*/
public void removeProfile(String path)
{
IProfileChangeListener pcl = null;
if (changeListenerHash.containsKey(path))
{
pcl = (IProfileChangeListener) changeListenerHash.get(path);
changeListenerHash.remove(path);
}
if (profiles.containsKey(path))
{
Profile p = getProfile(path);
p.clear();
if (pcl != null)
{
p.removeProfileChangeListener(pcl);
}
profiles.remove(path);
fireProfileChangeEvent(p);
applyProfiles();
}
}
/**
* setCurrentProfile
*
* @param path
*/
public void setCurrentProfile(String path)
{
Profile p = getProfile(path);
if (p == null)
{
p = getDefaultProfile();
}
setCurrentProfile(p);
}
/**
* Returns the default profile path
*
* @return - default profile
*/
public Profile getDefaultProfile()
{
return (Profile) profiles.get(DEFAULT_PROFILE_PATH);
}
/**
* setCurrentProfile
*
* @param profile
*/
public void setCurrentProfile(Profile profile)
{
this.currentProfile = profile;
fireProfileChangeEvent(this.currentProfile);
applyProfiles();
}
/**
* getCurrentProfile
*
* @return Profile
*/
public Profile getCurrentProfile()
{
return this.currentProfile;
}
/**
* isCurrentProfile
*
* @param profile
* @return boolean
*/
public boolean isCurrentProfile(Profile profile)
{
return this.currentProfile == profile;
}
/**
* addLanguageSupport
*
* @param mimeType
* @param lang
* @param factory
*/
public void addLanguageSupport(String mimeType, ILanguageEnvironment lang, IFileServiceFactory factory)
{
languageEnvironments.put(mimeType, lang);
if (!languageFactories.containsKey(mimeType))
{
languageFactories.put(mimeType, factory);
}
}
/**
* Returns an array of the current Profiles
*
* @return Profile[]
*/
public Profile[] getProfiles()
{
Profile[] array = (Profile[]) profiles.values().toArray(new Profile[0]);
return array;
}
/**
* Return the names of all the current profiles
*
* @return ProfileNames
*/
public String[] getProfilePaths()
{
return (String[]) profiles.keySet().toArray(new String[0]);
}
/**
* Returns the named Profile
*
* @param path
* @return Profile
*/
public Profile getProfile(String path)
{
return (Profile) profiles.get(path);
}
/**
* Return the total number of files across all profiles.
*
* @return TotalFileCount
*/
public int getTotalFileCount()
{
Profile[] profiles = getProfiles();
int count = 0;
for (int i = 0; i < profiles.length; i++)
{
count += profiles[i].getURIs().length;
}
return count;
}
/**
* fireProfileChangeEvent
*
* @param p
*/
public void fireProfileChangeEvent(Profile p)
{
for (int i = 0; i < listeners.size(); i++)
{
IProfileChangeListener listener = (IProfileChangeListener) listeners.get(i);
listener.onProfileChanged(p);
}
}
/**
* addProfileChangeListener
*
* @param l
*/
public void addProfileChangeListener(IProfileChangeListener l)
{
listeners.add(l);
}
/**
* removeProfileChangeListener
*
* @param l
*/
public void removeProfileChangeListener(IProfileChangeListener l)
{
listeners.remove(l);
}
/**
* fireProfileChangeEvent
*
* @param p
* @param state
*/
public void fireProfileAppliedEvent(ProfileURI p, boolean state)
{
for (int i = 0; i < listeners.size(); i++)
{
IProfileAppliedListener listener = (IProfileAppliedListener) appliedListeners.get(i);
listener.onProfileApplied(p, state);
}
}
/**
* addProfileChangeListener
*
* @param l
*/
public void addProfileAppliedListener(IProfileAppliedListener l)
{
appliedListeners.add(l);
}
/**
* removeProfileChangeListener
*
* @param l
*/
public void removeProfileChangeListener(IProfileAppliedListener l)
{
appliedListeners.remove(l);
}
/**
* Resets and applies the current profile (was applyProfileInternal)
*/
public void onUpdaterThreadUpdate()
{
resetIconStatus();
resetEnvironment();
resetAndApply();
}
/**
* resetIconStatus
*/
private void resetIconStatus()
{
if (currentProfile != null)
{
ProfileURI[] paths = currentProfile.getURIsIncludingChildren();
for (int i = 0; i < paths.length; i++)
{
fireProfileAppliedEvent(paths[i], false);
}
}
}
/**
* resetEnvironment
*/
public void resetEnvironment()
{
Collection langs = languageEnvironments.values();
for (Iterator iter = langs.iterator(); iter.hasNext();)
{
ILanguageEnvironment lang = (ILanguageEnvironment) iter.next();
lang.cleanEnvironment();
}
// let special file handlers reset their environments
for (ProfileFileTypeInfo info : ProfileFileTypeManager.getInstance().getAllInfos())
{
// NOTE: we get a null, for example, when the AIR license has expired
if (info.processor != null)
{
info.processor.cleanEnvironment();
}
}
}
/**
* resetAndApply
*/
private void resetAndApply()
{
if (currentProfile == null)
{
return;
}
// build hash maps
HashSet allURIs = new HashSet(Arrays.asList(FileContextManager.getKeySet()));
String activeEditorURI = CoreUIUtils.getActiveEditorURI();
HashSet currentProfileURIs = new HashSet(Arrays.asList(currentProfile.getURIsIncludingChildrenAsStrings()));
// remove active editor and all current profiles
allURIs.remove(activeEditorURI);
allURIs.remove(currentProfileURIs);
// Set file index of remaining uri's to -1
deactivateFileContexts(allURIs);
reindexCurrentProfile();
// make sure active editor is on top of all other files
reindexActiveEditor(activeEditorURI, currentProfileURIs);
}
/**
* deactivateFileContexts
*
* @param allURIs
*/
private void deactivateFileContexts(HashSet allURIs)
{
for (Iterator iter = allURIs.iterator(); iter.hasNext();)
{
String uri = (String) iter.next();
FileService fileContext = FileContextManager.get(uri);
if (fileContext != null)
{
IParseState parseState = fileContext.getParseState();
if (parseState != null)
{
parseState.setFileIndex(FileContextManager.DEFAULT_FILE_INDEX);
}
}
}
}
/**
* reindexActiveEditor
*
* @param activeEditorURI
* @param currentProfileURIs
*/
private void reindexActiveEditor(String activeEditorURI, HashSet currentProfileURIs)
{
FileService activeFileContext = FileContextManager.get(activeEditorURI);
if (activeFileContext != null && currentProfileURIs.contains(activeEditorURI) == false)
{
IParseState parseState = activeFileContext.getParseState();
if (parseState != null)
{
parseState.setFileIndex(FileContextManager.CURRENT_FILE_INDEX);
}
// This adds the contents to the environment
activeFileContext.forceContentChangedEvent();
}
}
/**
* reindexCurrentProfile
*
* @param currentProfileURIs
*/
private void reindexCurrentProfile()
{
int fileIndex = 0;
String[] currentProfileURIs = currentProfile.getURIsIncludingChildrenAsStrings();
String[] openEditorsArray = CoreUIUtils.getOpenEditorPaths();
Set<String> openEditors = new HashSet<String>(Arrays.asList(openEditorsArray));
for (String uri : currentProfileURIs)
{
// Only do this if there are any open editors, otherwise, there are
// no language factories
// so will throw exception
if (openEditorsArray.length > 0)
{
// See if we have a profile file processor for this file
// extension. Ultimately, all file types should be processed
// this way, but this was added to allow AIR to support
// script elements pointing to SWF files
String extension = FileUtils.getExtension(uri);
ProfileFileTypeInfo info = ProfileFileTypeManager.getInstance().getInfo(extension);
if (info != null && info.processor != null)
{
if (info.processor.processFile(uri, fileIndex++))
{
fireProfileAppliedEvent(new ProfileURI(uri, currentProfile), true);
continue;
}
}
// Effectively will only download items listed as *.js files
String mimeType = this.computeMIMEType(uri);
if (mimeType == null)
{
IdeLog.logInfo(
UnifiedEditorsPlugin.getDefault(),
StringUtils.format(Messages.ProfileManager_MimeTypeError, uri)
);
}
else
{
FileService fileContext = FileContextManager.get(uri);
if (fileContext == null ||
// !bug: this causes a full parse, but we haven't set
// the file index yet
(fileContext.getSourceProvider() instanceof DocumentSourceProvider && openEditors.contains(uri) == false))
{
IFileServiceFactory factory = (IFileServiceFactory) this.languageFactories.get(mimeType);
if (factory == null)
{
IdeLog.logError(
UnifiedEditorsPlugin.getDefault(),
StringUtils.format(Messages.ProfileManager_ServiceFactoryError, mimeType)
);
continue;
}
String path = CoreUIUtils.getPathFromURI(uri);
File file = new File(path);
if (file.exists() == false)
{
continue;
}
FileSourceProvider fsp = new FileSourceProvider(file);
fileContext = factory.createFileService(fsp);
FileContextManager.add(uri, fileContext);
}
// Set the file index
fileContext.getParseState().setFileIndex(fileIndex++);
// **NOTE:** we need to do a full parse here to get the lexemes
// etc refreshed if we don't do this, then @id files won't see each other on
// F3 as they are on the lexeme parse. Also they need to be profile wide.
// we can fix this soon....
fileContext.doFullParse();
// This adds the contents to the environment
fileContext.forceContentChangedEvent();
}
}
fireProfileAppliedEvent(new ProfileURI(uri, currentProfile), true);
}
}
/**
* computeMIMEType
*
* @param uri
* @return String
*/
private String computeMIMEType(String uri)
{
String mimeType = null; // TODO: we need to make this generic based on
// file registration
if (uri.toLowerCase().endsWith(".js") //$NON-NLS-1$
|| uri.toLowerCase().endsWith(".sdoc")) { //$NON-NLS-1$
mimeType = "text/javascript"; //$NON-NLS-1$
}
// else if (uri.endsWith(".htm") || uri.endsWith(".html"))
// {
// mimeType = "text/html";
// }
// else if (uri.endsWith(".css"))
// {
// mimeType = "text/css";
// }
return mimeType;
}
/**
* applyProfiles Sets the apply profile thread to dirty so that the internal method will be called
*/
public void applyProfiles()
{
if (_applyProfilesThread != null)
{
_applyProfilesThread.setDirty();
}
else
{
onUpdaterThreadUpdate();
}
}
/**
* refreshEnvironment
*/
public void refreshEnvironment()
{
Profile[] profiles = getProfiles();
for (int i = 0; i < profiles.length; i++)
{
fireProfileChangeEvent(profiles[i]);
}
applyProfiles();
}
/**
* @param profile
* @return - static profile
*/
public Profile makeProfileStatic(Profile profile)
{
String profileName = profile.getName();
String[] fileListArray = profile.getURIsAsStrings();
String path = profile.getURI();
boolean wasSelected = false;
if (path == this.getCurrentProfile().getURI())
{
wasSelected = true;
}
String newPath = staticProtocol + path;
removeProfile(path);
if (profileName.indexOf(titleLabel) != -1)
{
profileName = profileName.substring(0, profileName.length() - titleLabel.length());
}
Profile newProfile = createProfile(profileName, newPath);
newProfile.addURIs(fileListArray);
if (wasSelected)
{
setCurrentProfile(newProfile.getURI());
}
return newProfile;
}
}