/* * Copyright (c) 2009 Tom Parker <thpr@users.sourceforge.net> * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This program 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package pcgen.gui2.converter; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.StringTokenizer; import pcgen.base.util.DoubleKeyMap; import pcgen.base.util.DoubleKeyMapToList; import pcgen.cdom.base.Category; import pcgen.cdom.base.Constants; import pcgen.cdom.enumeration.AspectName; import pcgen.cdom.enumeration.RaceSubType; import pcgen.cdom.enumeration.RaceType; import pcgen.cdom.enumeration.Region; import pcgen.cdom.enumeration.SubClassCategory; import pcgen.cdom.enumeration.SubRace; import pcgen.cdom.enumeration.SubRegion; import pcgen.cdom.enumeration.Type; import pcgen.cdom.enumeration.VariableKey; import pcgen.core.Ability; import pcgen.core.AbilityCategory; import pcgen.gui2.converter.event.TokenProcessEvent; import pcgen.gui2.converter.event.TokenProcessorPlugin; import pcgen.system.PluginLoader; import pcgen.util.Logging; public class TokenConverter { private static final DoubleKeyMap<Class<?>, String, TokenProcessorPlugin> map = new DoubleKeyMap<>(); private static final DoubleKeyMap<Class<?>, String, Boolean> cached = new DoubleKeyMap<>(); private static final DoubleKeyMapToList<Class<?>, String, TokenProcessorPlugin> tokenCache = new DoubleKeyMapToList<>(); private static final DefaultTokenProcessor defaultProc = new DefaultTokenProcessor(); public static void addToTokenMap(TokenProcessorPlugin tpp) { TokenProcessorPlugin old = map.put(tpp.getProcessedClass(), tpp .getProcessedToken(), tpp); if (old != null) { Logging.errorPrint("More than one Conversion token for " + tpp.getProcessedClass().getSimpleName() + " " + tpp.getProcessedToken() + " found"); } } public static PluginLoader getPluginLoader() { return new PluginLoader() { @Override public void loadPlugin(Class<?> clazz) throws Exception { addToTokenMap((TokenProcessorPlugin) clazz.newInstance()); } @Override public Class[] getPluginClasses() { return new Class[] { TokenProcessorPlugin.class }; } }; } public static String process(TokenProcessEvent tpe) { Class<?> cl = tpe.getPrimary().getClass(); String key = tpe.getKey(); ensureCategoryExists(tpe); List<TokenProcessorPlugin> tokens = getTokens(cl, key); String error = ""; try { if (tokens != null) { for (TokenProcessorPlugin converter : tokens) { error += converter.process(tpe); if (tpe.isConsumed()) { break; } } } if (!tpe.isConsumed()) { error += defaultProc.process(tpe); } } catch (Exception ex) { Logging.errorPrint("Parse of " + tpe.getKey() + ":" + tpe.getValue() + " failed"); ex.printStackTrace(); } return tpe.isConsumed() ? null : error; } /** * If this is an ABILITY token, ensure that we have an ability category * in place to use for conversion. This is because the categories can be * declared in data which may not be being converted. * @param tpe The token event that is being processed. */ private static void ensureCategoryExists(TokenProcessEvent tpe) { if (!tpe.getKey().equals("ABILITY")) { return; } String value = tpe.getValue(); StringTokenizer tok = new StringTokenizer(value, Constants.PIPE); String cat = tok.nextToken(); Category<Ability> category = tpe.getContext().getReferenceContext() .silentlyGetConstructedCDOMObject(AbilityCategory.class, cat); if (category == null) { // Logging.log(Logging.INFO, "Found new cat " + cat + " in " + tpe); tpe.getContext().getReferenceContext().constructCDOMObject( AbilityCategory.class, cat); } } static class ConverterIterator implements Iterator<TokenProcessorPlugin> { private Class<?> rootClass; private final String tokenKey; private TokenProcessorPlugin nextToken = null; private boolean needNewToken = true; public ConverterIterator(Class<?> cl, String key) { rootClass = cl; tokenKey = key; } @Override public boolean hasNext() { setNextToken(); return !needNewToken; } protected void setNextToken() { if (needNewToken) { nextToken = null; while (nextToken == null && rootClass != null) { nextToken = grabToken(rootClass, tokenKey); rootClass = rootClass.getSuperclass(); } needNewToken = nextToken == null; } } protected TokenProcessorPlugin grabToken(Class<?> cl, String key) { return map.get(cl, key); } @Override public TokenProcessorPlugin next() { setNextToken(); if (needNewToken) { throw new NoSuchElementException(); } needNewToken = true; return nextToken; } @Override public void remove() { throw new UnsupportedOperationException( "Iterator does not support remove"); } } public static List<TokenProcessorPlugin> getTokens(Class<?> cl, String name) { List<TokenProcessorPlugin> list = tokenCache.getListFor(cl, name); if (!cached.containsKey(cl, name)) { for (Iterator<TokenProcessorPlugin> it = new ConverterIterator(cl, name); it.hasNext();) { TokenProcessorPlugin token = it.next(); tokenCache.addToListFor(cl, name, token); } list = tokenCache.getListFor(cl, name); cached.put(cl, name, Boolean.TRUE); } return list; } public static void clearConstants() { AspectName.clearConstants(); RaceSubType.clearConstants(); RaceType.clearConstants(); Region.clearConstants(); SubClassCategory.clearConstants(); SubRace.clearConstants(); SubRegion.clearConstants(); Type.clearConstants(); VariableKey.clearConstants(); } }