/******************************************************************************* * Copyright (c) 2000, 2008 QNX Software Systems 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 * QNX Software Systems - Initial API and implementation * Markus Schorn (Wind River Systems) *******************************************************************************/ package org.eclipse.cdt.internal.core; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.resources.IPathEntryVariableChangeListener; import org.eclipse.cdt.core.resources.IPathEntryVariableManager; import org.eclipse.cdt.core.resources.PathEntryVariableChangeEvent; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Preferences; import org.eclipse.core.runtime.SafeRunner; /** * Core's implementation of IPathEntryVariableManager. */ public class PathEntryVariableManager implements IPathEntryVariableManager { private Set<IPathEntryVariableChangeListener> listeners; private Preferences preferences; static final String VARIABLE_PREFIX = "pathEntryVariable."; //$NON-NLS-1$ /** * Constructor for the class. * * The current manager implementation is not used any more * Instead the CdtVarPathEntryVariableManager is used that actually wraps the CdtVariables contributed at workspace level * * NOTE: all PathEntryVariableManager functionality remains workable with the new * CdtVarPathEntryVariableManager. We could either remove this class or copy the contents of the * CdtVarPathEntryVariableManager to this class to preserve internal class name for better backward compatibility. * * */ private PathEntryVariableManager() { this.listeners = Collections.synchronizedSet(new HashSet<IPathEntryVariableChangeListener>()); this.preferences = CCorePlugin.getDefault().getPluginPreferences(); } /** * Note that if a user changes the key in the preferences file to be invalid * and then calls #getValue using that key, they will get the value back for * that. But then if they try and call #setValue using the same key it will throw * an exception. We may want to revisit this behaviour in the future. * * @see org.eclipse.cdt.core.resources.IPathEntryVariableManager#getValue(String) */ public IPath getValue(String varName) { String key = getKeyForName(varName); String value = preferences.getString(key); return value.length() == 0 ? null : Path.fromPortableString(value); } /** * @see org.eclipse.cdt.core.resources.IPathEntryVariableManager#setValue(String, IPath) */ public void setValue(String varName, IPath newValue) throws CoreException { //if the location doesn't have a device, see if the OS will assign one if (newValue != null && newValue.isAbsolute() && newValue.getDevice() == null) { newValue = new Path(newValue.toFile().getAbsolutePath()); } int eventType; // read previous value and set new value atomically in order to generate the right event synchronized (this) { IPath currentValue = getValue(varName); boolean variableExists = currentValue != null; if (currentValue == null) { if (newValue == null) { return; } } else if (currentValue.equals(newValue)) { return; } if (newValue == null) { preferences.setToDefault(getKeyForName(varName)); eventType = PathEntryVariableChangeEvent.VARIABLE_DELETED; } else { preferences.setValue(getKeyForName(varName), newValue.toPortableString()); eventType = variableExists ? PathEntryVariableChangeEvent.VARIABLE_CHANGED : PathEntryVariableChangeEvent.VARIABLE_CREATED; } } // notify listeners from outside the synchronized block to avoid deadlocks fireVariableChangeEvent(varName, newValue, eventType); } /** * Return a key to use in the Preferences. */ private String getKeyForName(String varName) { return VARIABLE_PREFIX + varName; } /** * @see org.eclipse.cdt.core.resources.IPathEntryVariableManager#resolvePath(IPath) */ public IPath resolvePath(IPath path) { if (path == null || path.segmentCount() == 0) { return path; } String variable = path.toPortableString(); if (variable.indexOf('$') == -1) { return path; } String value = expandVariable(variable); return (value == null || value.length() == 0) ? Path.EMPTY : new Path(value); } /** * Fires a property change event corresponding to a change to the * current value of the variable with the given name. * * @param name the name of the variable, to be used as the variable * in the event object * @param value the current value of the path variable or <code>null</code> if * the variable was deleted * @param type one of <code>IPathVariableChangeEvent.VARIABLE_CREATED</code>, * <code>PathEntryVariableChangeEvent.VARIABLE_CHANGED</code>, or * <code>PathEntryVariableChangeEvent.VARIABLE_DELETED</code> * @see PathEntryVariableChangeEvent * @see PathEntryVariableChangeEvent#VARIABLE_CREATED * @see PathEntryVariableChangeEvent#VARIABLE_CHANGED * @see PathEntryVariableChangeEvent#VARIABLE_DELETED */ private void fireVariableChangeEvent(String name, IPath value, int type) { if (this.listeners.size() == 0) return; // use a separate collection to avoid interference of simultaneous additions/removals Object[] listenerArray = this.listeners.toArray(); final PathEntryVariableChangeEvent pve = new PathEntryVariableChangeEvent(this, name, value, type); for (int i = 0; i < listenerArray.length; ++i) { final IPathEntryVariableChangeListener l = (IPathEntryVariableChangeListener) listenerArray[i]; ISafeRunnable job = new ISafeRunnable() { public void handleException(Throwable exception) { // already being logged in Platform#run() } public void run() throws Exception { l.pathVariableChanged(pve); } }; SafeRunner.run(job); } } /** * @see org.eclipse.core.resources.IPathVariableManager#getPathVariableNames() */ public String[] getVariableNames() { List<String> result = new LinkedList<String>(); String[] names = preferences.propertyNames(); for (String name : names) { if (name.startsWith(VARIABLE_PREFIX)) { String key = name.substring(VARIABLE_PREFIX.length()); result.add(key); } } return result.toArray(new String[result.size()]); } /** * @see org.eclipse.cdt.core.resources. * IPathEntryVariableManager#addChangeListener(IPathEntryVariableChangeListener) */ public void addChangeListener(IPathEntryVariableChangeListener listener) { listeners.add(listener); } /** * @see org.eclipse.cdt.core.resources. * IPathEntryVariableManager#removeChangeListener(IPathEntryVariableChangeListener) */ public void removeChangeListener(IPathEntryVariableChangeListener listener) { listeners.remove(listener); } /** * @see org.eclipse.core.resources.IPathVariableManager#isDefined(String) */ public boolean isDefined(String varName) { return getValue(varName) != null; } public void startup() { } public void shutdown() { } /** * Expand the variable with the format ${key}. example: * with variable HOME=/foobar * ${HOME}/project * The the return value will be /foobar/project. */ protected String expandVariable(String variable) { StringBuffer sb = new StringBuffer(); StringBuffer param = new StringBuffer(); char prev = '\n'; char ch = prev; boolean inMacro = false; boolean inSingleQuote = false; for (int i = 0; i < variable.length(); i++) { ch = variable.charAt(i); switch (ch) { case '\'': if (prev != '\\') { inSingleQuote = !inSingleQuote; } break; case '$' : if (!inSingleQuote && prev != '\\') { if (i < variable.length() && variable.indexOf('}', i) > 0) { char c = variable.charAt(i + 1); if (c == '{') { param.setLength(0); inMacro = true; prev = ch; continue; } } } break; case '}' : if (inMacro) { inMacro = false; String p = param.toString(); IPath path = getValue(p); if (path != null) { String v = path.toPortableString(); if (v != null) { sb.append(v); } } param.setLength(0); /* Skip the trailing } */ prev = ch; continue; } break; } /* switch */ if (!inMacro) { sb.append(ch); } else { /* Do not had the '{' */ if (!(ch == '{' && prev == '$')) { param.append(ch); } } prev = (ch == '\\' && prev == '\\') ? '\n' : ch; } /* for */ return sb.toString(); } }