// This file is part of PleoCommand: // Interactively control Pleo with psychobiological parameters // // Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Boston, USA. package pleocmd.cfg; import java.util.List; import pleocmd.Log; import pleocmd.RunnableWithArgument; import pleocmd.cfg.ConfigCollection.Type; import pleocmd.cfg.ConfigPath.PathType; import pleocmd.exc.ConfigurationException; import pleocmd.itfc.gui.Layouter; import pleocmd.pipe.PipePart; /** * Base class for a value inside a {@link PipePart}'s configuration. * <p> * All of the sub classes must have a constructor which uses only a label and * sets all other fields to default values and should have a constructor which * takes an initial content as an additional parameter.<br> * The constructors must not throw anything but {@link RuntimeException}s (like * {@link NullPointerException}, {@link IllegalArgumentException} or * {@link IndexOutOfBoundsException}). * <p> * All of the sub classes have something like getContent() to retrieve the * current value and setContent() to set it to a new one.<br> * Implementations of setContent() may throw {@link ConfigurationException} if * the input is invalid. * <p> * If the {@link ConfigValue} is single-lined ({@link #isSingleLined()} returns * true) it must support {@link #asString()} and {@link #setFromString(String)}, * and if it is multi-lined ( {@link #isSingleLined()} returns false) it must at * least support {@link #asString()}, {@link #asStrings()} and * {@link #setFromStrings(List)}, whereby "supporting" means not throwing an * {@link UnsupportedOperationException}. * * @author oliver */ public abstract class ConfigValue { private final String label; private RunnableWithArgument changingContent; protected ConfigValue(final String label) { this.label = label; } public final String getLabel() { return label; } @Override public final String toString() { final String str = asString(); return str == null ? getLabel() : getLabel() + ": " + str; } public abstract String asString(); public abstract void setFromString(final String string) throws ConfigurationException; abstract List<String> asStrings(); abstract void setFromStrings(final List<String> strings) throws ConfigurationException; public abstract String getIdentifier(); abstract boolean isSingleLined(); public abstract boolean insertGUIComponents(final Layouter lay); public abstract void setFromGUIComponents(); public abstract void setGUIEnabled(final boolean enabled); public abstract Object getContent(); public final void assign(final ConfigValue src) throws ConfigurationException { if (isSingleLined()) setFromString(src.asString()); else setFromStrings(src.asStrings()); } protected final void checkValidString(final String str, final boolean allowLineFeed) throws ConfigurationException { final String trimmed = str.trim(); if (!str.equals(trimmed) && !str.equals(trimmed + "\n")) throw new ConfigurationException("Cannot save this string " + "in a configuration file - will be trimmed: '%s'", str); if ("{".equals(trimmed)) throw new ConfigurationException("Cannot save this string " + "in a configuration file - must not equal '{': '%s'", str); if (str.contains("\0")) throw new ConfigurationException("Cannot save this string " + "in a configuration file - must not contain " + "null-terminator: '%s'", str); if (!allowLineFeed && str.contains("\n")) throw new ConfigurationException("Cannot save this string " + "in a configuration file - must not contain " + "line-feed: '%s'", str); if (allowLineFeed && (trimmed.contains("\n}\n") || trimmed.startsWith("}\n") || trimmed .endsWith("\n}"))) throw new ConfigurationException( "Cannot save this string in a configuration file - " + "no line must equal '}': '%s'", str); } // CS_IGNORE_NEXT NPath complexity - just a listing public static ConfigValue createValue(final String identifier, final String label, final boolean singleLined) { if ("int".equals(identifier)) return new ConfigInt(label); if ("long".equals(identifier)) return new ConfigLong(label); if ("double".equals(identifier)) return new ConfigDouble(label); if ("bool".equals(identifier)) return new ConfigBoolean(label); if ("dir".equals(identifier)) return new ConfigPath(label, PathType.Directory); if ("read".equals(identifier)) return new ConfigPath(label, PathType.FileForReading); if ("write".equals(identifier)) return new ConfigPath(label, PathType.FileForWriting); if ("bounds".equals(identifier)) return new ConfigBounds(label); if ("color".equals(identifier)) return new ConfigColor(label); if ("datablock".equals(identifier)) return new ConfigDataBlock(label); if ("list".equals(identifier)) return new ConfigCollection<String>(label, Type.List) { @Override protected String createItem(final String itemAsString) throws ConfigurationException { return itemAsString; } }; if ("set".equals(identifier)) return new ConfigCollection<String>(label, Type.Set) { @Override protected String createItem(final String itemAsString) throws ConfigurationException { return itemAsString; } }; if (identifier != null) Log.warn("Ignoring unknown identifier '%s' and " + "parsing value as simple string", identifier); return new ConfigString(label, !singleLined); } /** * Sets a method which is invoked during every modification of the GUI's * copy of the content of this {@link ConfigValue}, before the content has * been applied to the {@link ConfigValue} itself by clicking "OK" in the * GUI.<br> * The method <b>must not</b> modify content of other {@link ConfigValue}s * directly, but use methods like setContentGUI(...) instead, otherwise * clicking "Cancel" can no longer reset the changes made while the GUI has * been visible.<br> * The method is always called with one argument (it's type depends on the * {@link ConfigValue}'s subclass) for every GUI modification and once * during GUI creation.<br> * The return value may be one of: * <ul> * <li>null</li> * <li>a String, {@link #setFromString(String)} will be called</li> * <li>a List<String>, {@link #setFromStrings(List)} will be called</li> * </ul> * * @param changingContent * a {@link RunnableWithArgument} which is invoked on every GUI * driven change of the content. */ public final void setChangingContent( final RunnableWithArgument changingContent) { this.changingContent = changingContent; } @SuppressWarnings("unchecked") protected final void invokeChangingContent(final Object... args) { if (changingContent != null) { final Object res = changingContent.run(args); if (res instanceof String) try { setFromString((String) res); } catch (final ConfigurationException e) { Log.error(e); } else if (res instanceof List<?>) { final List<?> l = (List<?>) res; if (!l.isEmpty() && l.get(0) instanceof String) try { setFromStrings((List<String>) l); } catch (final ConfigurationException e) { Log.error(e); } } else if (res != null) Log.error("invokeChangingContent() can only handle null, " + "a String or a List of Strings as return value"); } } }