/*
* This software copyright by various authors including the RPTools.net
* development team, and licensed under the LGPL Version 3 or, at your option,
* any later version.
*
* Portions of this software were originally covered under the Apache Software
* License, Version 1.1 or Version 2.0.
*
* See the file LICENSE elsewhere in this distribution for license details.
*/
package net.rptools.maptool.client.ui.io;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.log4j.Logger;
/**
* <p>
* This class acts as the basis for various MapTool subsystems to store data in
* an external file.
* </p>
*
* <p>
* The idea is that each subsystem will instantiate this class and pass it to
* the {@code LoadSaveImpl} registry. When the {@code LoadSaveImpl} needs to ask
* the subsystem for data, it will call the {@code addDataObjects()} method.
* </p>
*
* <p>
* That method will take the string and data object and add entries to the tree.
* </p>
*
* <p>
* The subsystem accomplishes this by overriding {@code prepareForDisplay()} and
* calling {@code addDataObjects()}. There are multiple definitions of the
* latter method so that the user can pass two parameters and the compiler will
* figure out which to invoke.
* </p>
*
* <p>
* On the application side, the user has passed on an instantiated object, so
* when we're ready, we call {@code prepareForDisplay(UIBuilder.Tree(model)} and
* pass the tree model, where it is stored inside the object. When the {@code
* addDataObjects()} is done it will return to {@code prepareForDisplay()},
* which then returns to the application. The same object may be used again later.
* </p>
*
* <p>
* The MapTool object being stored should have a {@code toString()} method that
* produces the desired display string on the UI. This isn't strictly necessary, because
* for {@code Map}s the String key is used, but when the data structure is a
* {@code List} of some type, there is no key. This class copies all of the elements
* into a {@code Map} using the results of converting the object into a {@c
* String} as the key. The strings are then sorted and added to the UI.
* </p>
*
* @author crash
*
*/
abstract public class DataTemplate {
private static final Logger log = Logger.getLogger(DataTemplate.class);
/**
* <p>
* This method returns the string used to position the data within the tree displayed
* by the UI code.
* </p>
*
* <p>
* Normally this method will be overridden and simply return a hard-coded string.
* (Although it probably shouldn't be hard-coded, but read from a properties file.)
* </p>
*
* <p>
* However, if a subsystem has a large number of different data structures to save
* (such as the {@code CampaignProperties} class) then it won't be practical to
* use this method -- the string would need to be different for each category of
* campaign property, meaning a lot of almost identical child classes. :( Instead,
* this method can be ignored and the version of the {@code addDataObjects()}
* methods which take a {@code String} parameter can be used instead.
* </p>
*
* @return the string representing the tree path, such as <b>Campaign/Properties</b>
* or <b>Campaign/Maps</b>
*/
public String getTreePath() {
// The default string makes it clear that some software is misconfigured.
return "Misconfigured subsystem: getTreePath()";
}
abstract public String getSubsystemName();
private UIBuilder.TreeModel model;
/**
* <p>
* Thie method is invoked only by the UI application code. It starts the process of
* calling the user-defined {@code prepareForDisplay()} method that will actually
* fill the data structure.
* </p>
* @param m the data model to use to store information into the tree
*/
void populateModel(UIBuilder.TreeModel m) {
model = m;
log.debug("DataTemplate: processing " + this.getSubsystemName());
prepareForDisplay();
model = null;
}
/**
* <p>
* This method is overridden by the subsystem author when they have data they want
* to see persisted in an external file. They create this method and use it to
* invoke {@code addDataObjects()}, passing the data structure to the latter method.
* </p>
*
* <p>
* This sample code would be used when there are multiple different path strings
* needed by the subsystem. Ordinarily all of a subsystem's data would be under a
* single TreePath location in the model.
* <pre>
public void prepareForDisplay() {
addDataObjects("Campaign/Properties/Token Properties", cmpgn.getTokenTypeMap());
addDataObjects("Campaign/Properties/Repositories", cmpgn.getRemoteRepositoryList());
addDataObjects("Campaign/Properties/Sights", cmpgn.getSightTypeMap());
addDataObjects("Campaign/Properties/Lights", cmpgn.getLightSourcesMap());
addDataObjects("Campaign/Properties/States", cmpgn.getTokenStatesMap());
addDataObjects("Campaign/Properties/Bars", cmpgn.getTokenBarsMap());
addDataObjects("Campaign/Properties/Tables", cmpgn.getLookupTableMap());
addDataObjects("Campaign/Properties/Macros", cmpgn.getMacroButtonPropertiesArray());
}
* </pre>
*
* <p>
* This sample code would be used when there is but a single path string for a lot
* of different data objects.
* <pre>
public String getTreePath() {
return "Campaign/Maps";
}
public void prepareForDisplay() {
addDataObjects(Zone.getAllZones());
}
* </pre>
*/
abstract public void prepareForDisplay();
/**
* <p>
* When the subsystem's data is stored in a <code>Map</code>, use this method
* or the similar one that doesn't take a <code>String</code>.
* </p>
* <p>
* This method iterates through all elements in the map and adds a
* <code>MaptoolNode</code> for
* each one to the model. The keys are sorted alphabetically before being added to
* the list.
* </p>
* @param path the <code>String</code> representation of the TreePath
* @param data the <code>Map</code> to add to the UI
*/
protected final void addDataObjects(String path, Map<String, ? extends Object> data) {
String[] sorted = new String[data.size()];
data.keySet().toArray(sorted);
Arrays.sort(sorted);
for (int i = 0; i < sorted.length; i++) {
Object one = data.get(sorted[i]);
model.addNode(path, new MaptoolNode(sorted[i], one));
}
}
/**
* Calls {@link #addDataObjects(String, Map)} and passes <code>getTreePath()</code>
* as the first parameter.
*
* @param data the <code>Map</code> to add to the UI
*/
protected final void addDataObjects(Map<String, ? extends Object> data) {
addDataObjects(getTreePath(), data);
}
/**
* <p>
* When the subsystem's data is stored in something other than a <code>Map</code>,
* use this method or the similar one that doesn't take a <code>String</code>.
* </p>
* <p>
* This method iterates through all elements in the collection (usually a
* <code>List</code>) and adds a <code>MaptoolNode</code> for
* each one to the model. The keys are sorted alphabetically before being added to
* the list.
* </p>
* @param path the <code>String</code> representation of the TreePath
* @param data the <code>Collection</code> to add to the UI (usually a <code>List</code>)
*/
protected final void addDataObjects(String path, Collection<? extends Object> data) {
Map<String, Object> mymapping = new HashMap<String, Object>(data.size());
Iterator<? extends Object> iter = data.iterator();
while (iter.hasNext()) {
Object o = iter.next();
mymapping.put(o.toString(), o);
}
addDataObjects(path, mymapping);
}
/**
* Calls {@link #addDataObjects(String, Map)} and passes <code>getTreePath()</code>
* as the first parameter.
*
* @param data the <code>Collection</code> to add to the UI (usually a <code>List</code>)
*/
protected final void addDataObjects(Collection<? extends Object> data) {
addDataObjects(getTreePath(), data);
}
}