/*
* $Id$
*
* Copyright (c) 2004 by Rodney Kinney
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools;
import java.util.ArrayList;
import java.util.List;
import VASSAL.build.Configurable;
import VASSAL.build.GameModule;
/**
* Provides an XPath-like syntax for identifying configuration components
*/
public class ComponentPathBuilder {
private static ComponentPathBuilder instance;
public static ComponentPathBuilder getInstance() {
if (instance == null) {
instance = new ComponentPathBuilder();
}
return instance;
}
/**
* Return a string identifying the specified {@link Configurable}
* components as a paththrough the configuration parent-child hierarchy.
*
* @param targetPath
* @return
*/
public String getId(Configurable[] targetPath) {
SequenceEncoder se = new SequenceEncoder('/');
for (int i = 0; i < targetPath.length; ++i) {
String name = targetPath[i].getConfigureName();
SequenceEncoder se2 = new SequenceEncoder(targetPath[i].getClass().getName(), ':');
if (name != null) {
se2.append(name);
}
se.append(se2.getValue());
}
return se.getValue() == null ? "" : se.getValue();
}
/**
* Return a list of {@link Configurable} components specified by the
* given identifier.
*
* @param id
* @return
* @throws PathFormatException if no such component exists
*/
public Configurable[] getPath(String id) throws PathFormatException {
final ArrayList<Configurable> list = new ArrayList<Configurable>();
if (id.length() > 0) {
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(id, '/');
addToPath(GameModule.getGameModule(), st, list);
}
return list.toArray(new Configurable[list.size()]);
}
private void addToPath(Configurable parent,
SequenceEncoder.Decoder st,
List<Configurable> path)
throws PathFormatException {
if (st.hasMoreTokens()) {
String id = st.nextToken();
String name = null;
SequenceEncoder.Decoder st2 = new SequenceEncoder.Decoder(id, ':');
String className = st2.nextToken();
if (st2.hasMoreTokens()) {
name = st2.nextToken();
}
Configurable[] children = parent.getConfigureComponents();
Configurable match = null;
ArrayList<Configurable> partialMatches = new ArrayList<Configurable>();
int i = -1;
while (++i < children.length) {
if (className.equals(children[i].getClass().getName())) {
partialMatches.add(children[i]);
if (name == null ? children[i].getConfigureName() == null
: name.equals(children[i].getConfigureName())) {
match = children[i];
break;
}
}
}
if (match != null) {
path.add(match);
addToPath(match, st, path);
}
else if (!partialMatches.isEmpty()) {
if (!st.hasMoreTokens()) {
path.add(partialMatches.get(0));
}
else {
ArrayList<Configurable> subPath = null;
for (Configurable candidate : partialMatches) {
ArrayList<Configurable> l = new ArrayList<Configurable>();
try {
addToPath(candidate, st.copy(), l);
subPath = l;
// FIXME: adding to front of an ArrayList! Should we use LinkedList instead?
subPath.add(0, candidate);
break;
}
catch (PathFormatException e) {
// No match found here. Continue
}
}
if (subPath != null) {
path.addAll(subPath);
}
else {
findFailed(className, name, parent);
}
}
}
else {
findFailed(className, name, parent);
}
}
}
private void findFailed(String className, String name, Configurable parent)
throws PathFormatException {
String msgName = name;
if (msgName == null) {
msgName = className.substring(className.lastIndexOf('.') + 1);
}
throw new PathFormatException("Could not find " + msgName + " in " +
VASSAL.configure.ConfigureTree.getConfigureName(parent.getClass()));
}
public static class PathFormatException extends Exception {
private static final long serialVersionUID = 1L;
public PathFormatException(String message) {
super(message);
}
}
}