/*******************************************************************************
* Copyright (c) 2013 Google, Inc 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:
* Sergey Prigogin (Google) - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring.includes;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.XMLMemento;
import com.ibm.icu.text.Collator;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.internal.corext.codemanipulation.IncludeInfo;
/**
* A set of header file substitution rules.
*/
public class SymbolExportMap {
private static final String TAG_SYMBOL_EXPORT_MAPS = "maps"; //$NON-NLS-1$
private static final String TAG_SYMBOL_EXPORT_MAP = "map"; //$NON-NLS-1$
private static final String TAG_MAPPING = "mapping"; //$NON-NLS-1$
private static final String TAG_KEY = "key"; //$NON-NLS-1$
private static final String TAG_VALUE = "value"; //$NON-NLS-1$
private static final Collator COLLATOR = Collator.getInstance();
private final Map<String, Set<IncludeInfo>> map;
public SymbolExportMap() {
this.map = new HashMap<String, Set<IncludeInfo>>();
}
/**
* @param keysAndValues an array of keys and values: [key1, value1, key2, value2, ...].
* Keys and values may be optionally surrounded by double quotes or angle brackets.
* Angle brackets indicate a system include.
*/
public SymbolExportMap(String[] keysAndValues) {
if (keysAndValues.length % 2 != 0)
throw new IllegalArgumentException("More keys than values"); //$NON-NLS-1$
this.map = new HashMap<String, Set<IncludeInfo>>(keysAndValues.length / 2);
for (int i = 0; i < keysAndValues.length;) {
String key = keysAndValues[i++];
addMapping(key, keysAndValues[i++]);
}
}
public SymbolExportMap(SymbolExportMap other) {
this.map = new HashMap<String, Set<IncludeInfo>>(other.map.size());
addAllMappings(other);
}
/**
* Indicates that the given symbol is exported by the given header.
* @param symbol The symbol represented by its fully qualified name.
* @param header The header file exporting the symbol.
*/
protected void addMapping(String symbol, IncludeInfo header) {
if (symbol.equals(header))
return; // Don't allow mapping to itself.
Set<IncludeInfo> list = map.get(symbol);
if (list == null) {
list = new LinkedHashSet<IncludeInfo>();
map.put(symbol, list);
}
list.add(header);
}
/**
* Indicates that the given symbol is exported by the given header.
* @param symbol The symbol represented by its fully qualified name.
* @param header The header file exporting the symbol. The header is represented by an include
* name optionally surrounded by double quotes or angle brackets. Angle brackets indicate
* a system include.
*/
public void addMapping(String symbol, String header) {
addMapping(symbol, new IncludeInfo(header));
}
/**
* Returns header files that should be used instead of the given one.
*
* @param from The header file to be replaced. A system header has to match exactly.
* A non-system header matches both, non-system and system headers.
* @return The list of header files ordered by decreasing preference.
*/
public Set<IncludeInfo> getMapping(String from) {
Set<IncludeInfo> list = map.get(from);
if (list == null)
return Collections.emptySet();
return list;
}
/**
* Removes exporting headers for a given symbol.
*
* @param symbol the header file to remove exporting headers for
* @return the previous header associated with the symbol, or {@code null} if there were no
* exporting headers.
*/
public Set<IncludeInfo> removeMapping(String symbol) {
return map.remove(symbol);
}
/**
* Writes the map to a memento.
*/
public void saveToMemento(IMemento memento) {
List<String> keys = new ArrayList<String>(map.keySet());
Collections.sort(keys, COLLATOR);
for (String key : keys) {
List<IncludeInfo> values = new ArrayList<IncludeInfo>(map.get(key));
Collections.sort(values);
for (IncludeInfo value : values) {
IMemento mapping = memento.createChild(TAG_MAPPING);
mapping.putString(TAG_KEY, key);
mapping.putString(TAG_VALUE, value.toString());
}
}
}
public static SymbolExportMap fromMemento(IMemento memento) {
SymbolExportMap includeMap = new SymbolExportMap();
for (IMemento mapping : memento.getChildren(TAG_MAPPING)) {
String key = mapping.getString(TAG_KEY);
includeMap.addMapping(key, mapping.getString(TAG_VALUE));
}
return includeMap;
}
public void addAllMappings(SymbolExportMap other) {
for (Entry<String, Set<IncludeInfo>> entry : other.map.entrySet()) {
String source = entry.getKey();
Set<IncludeInfo> otherTargets = entry.getValue();
Set<IncludeInfo> targets = map.get(source);
if (targets == null) {
targets = new LinkedHashSet<IncludeInfo>(otherTargets);
map.put(source, targets);
} else {
targets.addAll(otherTargets);
}
}
}
/** For debugging only. */
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
ArrayList<String> symbols = new ArrayList<String>(map.keySet());
Collections.sort(symbols);
for (String symbol : symbols) {
buf.append('\n');
buf.append(symbol);
buf.append(" exported by "); //$NON-NLS-1$
List<IncludeInfo> targets = new ArrayList<IncludeInfo>(map.get(symbol));
for (int i = 0; i < targets.size(); i++) {
if (i > 0)
buf.append(", "); //$NON-NLS-1$
buf.append(targets.get(i));
}
}
return buf.toString();
}
public Map<String, Set<IncludeInfo>> getMap() {
return Collections.unmodifiableMap(map);
}
public static String serializeMaps(List<SymbolExportMap> maps) {
XMLMemento memento = XMLMemento.createWriteRoot(TAG_SYMBOL_EXPORT_MAPS);
for (SymbolExportMap element : maps) {
element.saveToMemento(memento.createChild(TAG_SYMBOL_EXPORT_MAP));
}
StringWriter writer = new StringWriter();
try {
memento.save(writer);
} catch (IOException e) {
CUIPlugin.log(e);
}
return writer.toString();
}
public static List<SymbolExportMap> deserializeMaps(String str) {
StringReader reader = new StringReader(str);
XMLMemento memento;
try {
memento = XMLMemento.createReadRoot(reader);
} catch (WorkbenchException e) {
return Collections.emptyList();
}
List<SymbolExportMap> maps = new ArrayList<SymbolExportMap>();
for (IMemento element : memento.getChildren(TAG_SYMBOL_EXPORT_MAP)) {
maps.add(fromMemento(element));
}
return maps;
}
}