net.sf.cuf.ui.builder.SwingXMLBuilder Maven / Gradle / Ivy
The newest version!
/**************************************************************************
* P A C K A G E *
**************************************************************************/
package net.sf.cuf.ui.builder;
/**************************************************************************/
/**************************************************************************
* I M P O R T S *
**************************************************************************/
import org.jdom2.input.SAXBuilder;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Attribute;
import org.jdom2.Parent;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import javax.swing.JComponent;
import java.io.InputStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Locale;
import java.util.ResourceBundle;
import java.awt.Container;
import java.awt.Component;
import net.sf.cuf.ui.SwingDecoratorFunctionality;
import net.sf.cuf.state.State;
import net.sf.cuf.model.ValueModel;
/**************************************************************************/
/**************************************************************************
* C L A S S E S *
**************************************************************************/
/**************************************************************************/
/**
* SwingXMLBuilder generates a visual representation from a simple XML
* description.
* It also can create non-visble components, and connect the actions
* of the visuable components to any methods of the non-visual objects.
* Besides generating arbitrary objects (as long as the class has a public
* default constructor), also state handling and data binding objects can
* generated.
* It is somehow like the legendary "NIB-Files" from the NeXT/Apple
* InterfaceBuilder.app, unfortunately without the interactive UI Builder :-(
*
* The state handling stuff centers around the {@link net.sf.cuf.state.State}
* and {@link net.sf.cuf.state.StateAdapter} interfaces, and can be used
* to describe logical states and bind them to the widgets.
*
* The data binding stuff centers around the {@link net.sf.cuf.model.ValueModel}
* interface, and can be used to bind the content of POJO's to widgets,
* even if the POJO's contain complex lists or a type conversion is required.
*
* Both visual widgets as well as non-visual objects can be referred by name,
* see SwingMapping for a detailed description of how this works.
* The "id" Attribute of the various elements are mapped to the name of a
* widget, see JComponent.setName() for further information.
*
* This class is somehow an alternative to SwingMapping+SwingDecorator,
* because the XML description contains all the information a SwingDecorator
* would normally decorate, and a SwingXMLBuilder has the same
* key/value methods like SwingMapping.
*
* The non-visual objects only have a flat (non-hierarchical) namespace.
* It would be very easy to fix that, but because there is no generic
* "add to parent" method, it generates little value.
* If the non-visual object implements the SwingXMLBuilder.Backlink interface,
* the "void setSwingXMLBuilder(SwingXMLBuilder pBuilder)"
* method is called with from the builder that created the object.
*
* FIXME: Add support for SpringLayout
* FIXME: All layout constants should have symbolic names
* FIXME: Add depending filling to the popup menu support, and allow more than
* one popup per widget
*
* @author Jürgen Zeller, [email protected]
*/
public class SwingXMLBuilder
{
/** our DTD SYSTEM ID for version 1.4 */
public static final String SYSTEM_ID_1_4 = "http://www.sdm.com/dtd/xml2swing-1.4.dtd";
/** file where expect the DTD for version 1.4 */
private static final String SYSTEM_ID_1_4_FILE = "xml2swing-1.4.dtd";
/** our DTD SYSTEM ID for version 1.5 */
public static final String SYSTEM_ID_1_5 = "http://www.sdm.com/dtd/xml2swing-1.5.dtd";
/** file where expect the DTD for version 1.5 */
private static final String SYSTEM_ID_1_5_FILE = "xml2swing-1.5.dtd";
/** our DTD SYSTEM ID for version 1.6 */
public static final String SYSTEM_ID_1_6 = "http://www.sdm.com/dtd/xml2swing-1.6.dtd";
/** file where expect the DTD for version 1.6 */
private static final String SYSTEM_ID_1_6_FILE = "xml2swing-1.6.dtd";
/** our DTD SYSTEM ID for version 1.7 */
public static final String SYSTEM_ID_1_7 = "http://www.sdm.com/dtd/xml2swing-1.7.dtd";
/** file where expect the DTD for version 1.7 */
private static final String SYSTEM_ID_1_7_FILE = "xml2swing-1.7.dtd";
/** our DTD SYSTEM ID for version 1.8 */
public static final String SYSTEM_ID_1_8 = "http://www.sdm.com/dtd/xml2swing-1.8.dtd";
/** file where expect the DTD for version 1.8 */
private static final String SYSTEM_ID_1_8_FILE = "xml2swing-1.8.dtd";
/** our DTD SYSTEM ID for version 1.9 */
public static final String SYSTEM_ID_1_9 = "http://www.sdm.com/dtd/xml2swing-1.9.dtd";
/** file where expect the DTD for version 1.9 */
private static final String SYSTEM_ID_1_9_FILE = "xml2swing-1.9.dtd";
/** our DTD SYSTEM ID for version 2.0 */
public static final String SYSTEM_ID_2_0 = "http://www.sdm.com/dtd/xml2swing-2.0.dtd";
/** file where expect the DTD for version 2.0 */
private static final String SYSTEM_ID_2_0_FILE = "xml2swing-2.0.dtd";
/** our DTD SYSTEM ID for version 2.1 */
public static final String SYSTEM_ID_2_1 = "http://www.sdm.com/dtd/xml2swing-2.1.dtd";
/** file where expect the DTD for version 2.1 */
private static final String SYSTEM_ID_2_1_FILE = "xml2swing-2.1.dtd";
/** flag if we validate XML input (false: no validation, faster!) */
private static final boolean XML_VALIDATE = true;
/**
* Registry to be used for the widget creation, also the factory to be used for creation
*/
private static WidgetFactoryRegistry sWidgetFactoryRegistry;
/** separator of component names */
public static final String SEPARATOR = "/";
// non-visual elements
public static final String OBJECT_ELEMENT = "object";
public static final String ACTION_ELEMENT = "action";
public static final String RESOURCE_ELEMENT = "resource";
// action specific elements
public static final String IS_TOGGLE_ELEMENT = "isToggle";
/** Default id for loaded resources. */
public static final String DEFAULT_ID_RESOURCE = "ResourceBundle";
// widgets
public static final String FRAME_ELEMENT = "frame";
public static final String DIALOG_ELEMENT = "dialog";
public static final String PANEL_ELEMENT = "panel";
public static final String ANY_ELEMENT = "any";
public static final String BUTTON_ELEMENT = "button";
public static final String LABEL_ELEMENT = "label";
public static final String RADIOBUTTON_ELEMENT = "radiobutton";
public static final String TOGGLEBUTTON_ELEMENT = "togglebutton";
public static final String CHECKBOX_ELEMENT = "checkbox";
public static final String TEXTFIELD_ELEMENT = "textfield";
public static final String PASSWORDFIELD_ELEMENT = "passwordfield";
public static final String TEXTAREA_ELEMENT = "textarea";
public static final String COMBOBOX_ELEMENT = "combobox";
public static final String SLIDER_ELEMENT = "slider";
public static final String SPLITPANE_ELEMENT = "splitpane";
public static final String SCROLLPANE_ELEMENT = "scrollpane";
public static final String TABBEDPANE_ELEMENT = "tabbedpane";
public static final String LIST_ELEMENT = "list";
public static final String TABLE_ELEMENT = "table";
public static final String TREE_ELEMENT = "tree";
public static final String SEPARATOR_ELEMENT = "separator";
public static final String SEPARATORPANEL_ELEMENT= "separatorpanel";
public static final String MENUBAR_ELEMENT = "menubar";
public static final String MENU_ELEMENT = "menu";
public static final String MENU_ITEM_ELEMENT = "menuitem";
public static final String TOOLBAR_ELEMENT = "toolbar";
public static final String TOOLBAR_ITEM_ELEMENT = "toolbaritem";
public static final String POPUP_ELEMENT = "popup";
public static final String POPUPMENU_ELEMENT = "popupmenu";
public static final String POPUP_ITEM_ELEMENT = "popupitem";
public static final String BUILDER_ELEMENT = "builder";
public static final String SPINNER_ELEMENT = "spinner";
// decorations
public static final String PROPERTY_ELEMENT = "property";
public static final String TEXT_ELEMENT = "text";
public static final String TITLE_ELEMENT = "title";
public static final String MNEMONIC_ELEMENT = "mnemonic";
public static final String SHORTCUT_ELEMENT = "shortcut";
public static final String ACCELERATOR_ELEMENT = "accelerator";
public static final String TOOLTIP_ELEMENT = "tooltip";
public static final String ICON_ELEMENT = "icon";
public static final String COMBOITEM_ELEMENT = "comboitem";
public static final String TABMAPPING_ELEMENT = "tabmapping";
public static final String TABITEM_ELEMENT = "tabitem";
public static final String POPUPREF_ELEMENT = "popupref";
public static final String LAYOUTMANAGER_ELEMENT = "layoutmanager";
public static final String CONSTRAINT_ELEMENT = "constraint";
public static final String BORDER_ELEMENT = "border";
public static final String DLU_ELEMENT = "dlu";
public static final String MINSIZE_ELEMENT = "minsize";
public static final String MAXSIZE_ELEMENT = "maxsize";
public static final String PREFSIZE_ELEMENT = "prefsize";
// connections
public static final String BUTTONACTION_ELEMENT = "buttonAction";
public static final String COMBOBOXACTION_ELEMENT = "comboboxAction";
public static final String TEXTFIELDACTION_ELEMENT = "textfieldAction";
public static final String MENUACTION_ELEMENT = "menuAction";
public static final String ACTIONACTION_ELEMENT = "actionAction";
public static final String CARETACTION_ELEMENT = "caretAction";
public static final String CHANGEACTION_ELEMENT = "changeAction";
public static final String FOCUSACTION_ELEMENT = "focusAction";
public static final String LISTSELECTIONACTION_ELEMENT = "listSelectionAction";
public static final String PROPERTYCHANGEACTION_ELEMENT = "propertyChangeAction";
public static final String TREESELECTIONACTION_ELEMENT = "treeSelectionAction";
public static final String DOCUMENTACTION_ELEMENT = "documentAction";
// state handling
public static final String STATE_ELEMENT = "state";
public static final String EXPRESSION_ELEMENT = "expression";
public static final String STATEADAPTER_ELEMENT = "stateAdapter";
public static final String SETSTATE_ELEMENT = "setstate";
public static final String ADAPTEE_ELEMENT = "adaptee";
// data binding
public static final String VALUEHOLDER_ELEMENT = "valueholder";
public static final String BUFFEREDHOLDER_ELEMENT = "bufferedholder";
public static final String ASPECTADAPTER_ELEMENT = "aspectadapter";
public static final String INDEXEDADAPTER_ELEMENT = "indexedadapter";
public static final String INDEXINLIST_ELEMENT = "indexinlist";
public static final String PROPERTIESADAPTER_ELEMENT = "propertiesadapter";
public static final String TYPECONVERTER_ELEMENT = "typeconverter";
public static final String FORMATCONVERTER_ELEMENT = "formatconverter";
public static final String REGEXPCONVERTER_ELEMENT = "regexpconverter";
public static final String CONVERTERINSYNC_ELEMENT = "converterinsync";
public static final String SELECTIONINLIST_ELEMENT = "selectioninlist";
public static final String MULTISELECTIONINLIST_ELEMENT = "multiselectioninlist";
public static final String SETVALUE_ELEMENT = "setvalue";
public static final String DOCUMENTADAPTER_ELEMENT = "documentadapter";
public static final String BUTTONADAPTER_ELEMENT = "buttonadapter";
public static final String COMBOBOXADAPTER_ELEMENT = "comboboxadapter";
public static final String TABLEADAPTER_ELEMENT = "tableadapter";
public static final String LISTADAPTER_ELEMENT = "listadapter";
public static final String FILTERINGLISTADAPTER_ELEMENT = "filteringlistadapter";
public static final String COLUMN_ELEMENT = "column";
public static final String LOVMAPPER_ELEMENT = "lovmapper";
public static final String TOOLTIPADAPTER_ELEMENT = "tooltipadapter";
public static final String LABELADAPTER_ELEMENT = "labeladapter";
public static final String LOVADAPTER_ELEMENT = "lovadapter";
// attributes
public static final String KEY_ATTRIBUTE = "key";
public static final String VALUE_ATTRIBUTE = "value";
public static final String BOOLEAN_ATTRIBUTE = "boolean";
public static final String INT_ATTRIBUTE = "int";
public static final String REF_ATTRIBUTE = "ref";
public static final String REF2_ATTRIBUTE = "ref2";
public static final String ID_ATTRIBUTE = "id";
public static final String MIN_ATTRIBUTE = "min";
public static final String MAX_ATTRIBUTE = "max";
public static final String ACTIONREF_ATTRIBUTE = "actionref";
public static final String CLOSEACTION_ATTRIBUTE = "closeAction";
public static final String ESCAPEACTION_ATTRIBUTE= "escapeAction";
public static final String MODAL_ATTRIBUTE = "modal";
public static final String CLASS_ATTRIBUTE = "class";
public static final String FILTER_CLASS_ATTRIBUTE = "filterClass";
public static final String CONSTANT_ATTRIBUTE = "constant";
public static final String BASENAME_ATTRIBUTE = "basename";
public static final String DEFAULT_ATTRIBUTE = "default";
public static final String LANG_ATTRIBUTE = "lang";
public static final String TYPE_ATTRIBUTE = "type";
public static final String NAME_ATTRIBUTE = "name";
public static final String ORIENTATION_ATTRIBUTE = "orientation";
public static final String ONETOUCHEXPANDABLE_ATTRIBUTE= "oneTouchExpandable";
public static final String TABPLACEMENT_ATTRIBUTE= "tabPlacement";
public static final String SOURCE_ATTRIBUTE = "source";
public static final String TARGET_ATTRIBUTE = "target";
public static final String METHOD_ATTRIBUTE = "method";
public static final String ENABLED_ATTRIBUTE = "enabled";
public static final String SELECTED_ATTRIBUTE = "selected";
public static final String VERTICAL_SCROLLBAR_ATTRIBUTE = "verticalScrollBar";
public static final String HORIZONTAL_SCROLLBAR_ATTRIBUTE = "horizontalScrollBar";
public static final String ALIGN_ATTRIBUTE = "align";
public static final String LABELFOR_ATTRIBUTE = "labelfor";
public static final String COMMAND_ATTRIBUTE = "command";
public static final String DOCUMENT_ATTRIBUTE = "document";
public static final String COLUMNS_ATTRIBUTE = "columns";
public static final String MASKFORMAT_ATTRIBUTE = "maskFormat";
public static final String DATEFORMAT_ATTRIBUTE = "dateFormat";
public static final String NUMBERFORMAT_ATTRIBUTE= "numberFormat";
public static final String EDITABLE_ATTRIBUTE = "editable";
public static final String LINEWRAP_ATTRIBUTE = "linewrap";
public static final String INVERT_ATTRIBUTE = "invert";
public static final String SUBJECT_ATTRIBUTE = "subject";
public static final String TRIGGER_ATTRIBUTE = "trigger";
public static final String DEEPCOPY_ATTRIBUTE = "deepcopy";
public static final String SETPREFIX_ATTRIBUTE = "setprefix";
public static final String GETPREFIX_ATTRIBUTE = "getprefix";
public static final String ACCESS_ATTRIBUTE = "access";
public static final String ACCESSREF_ATTRIBUTE = "accessref";
public static final String MODELREF_ATTRIBUTE = "modelref";
public static final String PREF_WIDTH_ATTRIBUTE = "prefwidth";
public static final String WIDGETREF_ATTRIBUTE = "widgetref";
public static final String FORMAT_ATTRIBUTE = "format";
public static final String BLOCK_ATTRIBUTE = "block";
public static final String MODIFIERS_ATTRIBUTE = "modifiers";
public static final String CONDITION_ATTRIBUTE = "condition";
public static final String SORTABLE_ATTRIBUTE = "sortable";
public static final String SORTMODELREF_ATTRIBUTE= "sortmodelref";
public static final String COMPARATORCLASS_ATTRIBUTE= "comparatorclass";
public static final String INITIAL_SORTING_COLUMN_ATTRIBUTE = "initialsortingcolumn";
public static final String DEEP_ATTRIBUTE = "deep";
public static final String OPAQUE_ATTRIBUTE = "opaque";
public static final String LOVSREF_ATTRIBUTE = "lovsref";
public static final String ITEMSREF_ATTRIBUTE = "itemsref";
public static final String KEYSREF_ATTRIBUTE = "keysref";
public static final String ITEMS_ATTRIBUTE = "items";
public static final String KEYS_ATTRIBUTE = "keys";
public static final String UNSELECTED_ATTRIBUTE = "unselected";
public static final String IMPORTNAMES_ATTRIBUTE = "importNames";
public static final String EXT_UPD_REF_ATTRIBUTE = "extUpdRef";
// to make the handling of jgoodies form builders easy, we store the
// builder in the properties of the component it operates on
public static final String FORMBUILDER_PROPERTY = "formbuilder";
// to make the creation of radio buttons easy, we store temporarly
// a hashmap for the tab-mapping in the JTabbedPane properties
public static final String TABMAPPING_PROPERTY = "tabmapping";
// we store the "ESCAPE"-Action in the JFrame/JDialog under that name
public static final String CANCEL_ACTION_KEY = "CANCEL_ACTION_KEY";
// to make the creation of radio buttons easy, we store the ButtonGroup
// in their parents properties
public static final String BUTTONGROUP_PROPERTY = "buttongroup";
// marker for radio buttons to preselect the pressed one
public static final String BUTTONGROUP_TRUE = "true";
// values for the layoutmanager and constraint type attribute and other elements
public static final String GRIDBAG_LAYOUT = "gridbag";
public static final String NULL_LAYOUT = "null";
public static final String BORDER_LAYOUT = "border";
public static final String BOX_LAYOUT = "box";
public static final String CARD_LAYOUT = "card";
public static final String FLOW_LAYOUT = "flow";
public static final String GRID_LAYOUT = "grid";
public static final String TABLE_LAYOUT = "table";
public static final String FORM_LAYOUT = "form";
public static final String FLEXIBLEGRID_LAYOUT = "flexiblegrid";
public static final String HGAP_ELEMENT = "hgap";
public static final String VGAP_ELEMENT = "vgap";
public static final String AXIS_ELEMENT = "axis";
public static final String ALIGN_ELEMENT = "align";
public static final String COLUMNS_ELEMENT = "columns";
public static final String ROWS_ELEMENT = "rows";
public static final String COLUMNGROUPS_ELEMENT = "columngroups";
public static final String ROWGROUPS_ELEMENT = "rowgroups";
public static final String LINEBREAK_ELEMENT = "linebreak";
public static final String TABLELAYOUT_FILL = "fill";
public static final String TABLELAYOUT_PREFERRED = "preferred";
public static final String TABLELAYOUT_MINIMUM = "minimum";
// values for the border type attribute and elements
public static final String LINE_ATTRIBUTE = "line";
public static final String ETCHED_ATTRIBUTE = "etched";
public static final String BEVEL_ATTRIBUTE = "bevel";
public static final String EMPTY_ATTRIBUTE = "empty";
public static final String MATTE_ATTRIBUTE = "matte";
public static final String COLOR_ELEMENT = "color";
public static final String THICKNESS_ELEMENT = "thickness";
public static final String SUNKEN_ATTRIBUTE = "sunken";
public static final String SUNKEN_RAISED = "raised";
public static final String SUNKEN_LOWERED = "lowered";
public static final String SCROLLBAR_NEVER = "never";
public static final String SCROLLBAR_ALWAYS = "always";
public static final String SCROLLBAR_AS_NEEDED = "asNeeded";
// XML root element
public static final String ROOT_ELEMENT = "xml2swing";
// XML non-visual element, first under the ROOT_ELEMENT, optional
public static final String NONVISUAL_ELEMENT = "nonvisual";
// XML visual element, second under the ROOT_ELEMENT, optional
public static final String VISUAL_ELEMENT = "visual";
// XML connect element, third under the ROOT_ELEMENT, optional
public static final String CONNECT_ELEMENT = "connect";
// XML statehandling element, forth under the ROOT_ELEMENT, optional
public static final String STATEHANDLING_ELEMENT = "statehandling";
// XML databinding element, fifth under the ROOT_ELEMENT, optional
public static final String DATABINDING_ELEMENT = "databinding";
/*
* start of non-static members
*/
/** map, key= hierarchical name, value= JComponent/JFrame/JDialog */
private Map mNameToVisual;
/** map, key= short name, value= JComponent/JFrame/JDialog */
private Map mShortNameToVisual;
/** map, key= JComponent/JFrame/JDialog, value= (hierarchical) name */
private Map mVisualToName;
/** map, key= name, value= non-visual object */
private Map mNameToNonVisual;
/** cache for our icons */
private IconCache mIconCache;
/** A decorator for widgets and actions. The decorator is created lazily if a ResourceBundle is found. */
private SwingDecoratorFunctionality mSwingDecorator;
/** Indicator if we already tried to create the {@link #mSwingDecorator}. */
private boolean mSwingDecoratorCreated;
/** Locale used for this instance of the SwingXMLBuilder. Is set to {@link Locale#getDefault} upon creation. */
private Locale mLocale;
/** Marker if the SwingXMLBuilder was disposed and shouldn't be used any longer. */
private boolean mIsDisposed;
/**
* Determine a decorator for widgets and actions. The decorator is
* instantiated lazily upon the first call to this method (which happens
* implicitely when actions or visuals are created). To be constructed it
* needs one or more non-visual {@link ResourceBundle} whose ids begin with
* {@link #DEFAULT_ID_RESOURCE}. Otherwise this method returns
* null
and no other attempt is made to construct a decorator.
* @return decorator or null
*/
public SwingDecoratorFunctionality getSwingDecorator()
{
if ( !mSwingDecoratorCreated )
{
mSwingDecoratorCreated = true;
// look for ResourceBundles with the right id
List rbs = new ArrayList<>();
for (final Object o : getNameToNonVisual().keySet())
{
String name = (String) o;
if (name != null && name.startsWith(DEFAULT_ID_RESOURCE))
{
ResourceBundle rb = (ResourceBundle) getNonVisualObject(name); // check for correct type
rbs.add(rb);
}
}
// instantiate the decorator if we found any
if (!rbs.isEmpty())
{
mSwingDecorator = new SwingDecoratorFunctionality(false, getIconCache());
for (final Object rb : rbs)
{
mSwingDecorator.addBundle((ResourceBundle) rb);
}
}
}
return mSwingDecorator;
}
/**
* Determine the locale used for this instance of the SwingXMLBuilder.
* @return used locale
*/
public Locale getLocale()
{
return mLocale;
}
/**
* widget registry stuff
* @return the widget factory, never null
*/
public static WidgetFactoryRegistry getWidgetFactoryRegistry()
{
if (sWidgetFactoryRegistry==null)
{
// initializes the WidgetFactoryRegistry
sWidgetFactoryRegistry= new WidgetFactoryRegistry();
}
return sWidgetFactoryRegistry;
}
/*
* create stuff
*/
/**
* SwingXMLBuilder has a private constructor, because instances are created
* with the create() methods.
* @param pLocale locale to use or null
if the current default locale is to be used
*/
private SwingXMLBuilder(Locale pLocale)
{
if (pLocale == null)
{
pLocale = Locale.getDefault();
}
mNameToVisual = new HashMap<>();
mShortNameToVisual= new HashMap<>();
mVisualToName = new HashMap<>();
mNameToNonVisual = new HashMap<>();
mIconCache = new IconCache();
mLocale = pLocale;
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pElement start point ("root") of the process
* @throws IllegalArgumentException if pElement is null, or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final Element pElement) throws IllegalArgumentException
{
return create(pElement, null, null);
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pElement start point ("root") of the process
* @param pLocale locale to use or null
if the current default locale is to be used
* @throws IllegalArgumentException if pElement is null, or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final Element pElement, final Locale pLocale) throws IllegalArgumentException
{
return create(pElement, pLocale, null);
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pElement start point ("root") of the process
* @param pNonVisual null or non-visual key/object mapping
* @throws IllegalArgumentException if pElement is null, or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final Element pElement, final Map pNonVisual) throws IllegalArgumentException
{
return create(pElement, null, pNonVisual);
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pElement start point ("root") of the process
* @param pLocale locale to use or null
if the current default locale is to be used
* @param pNonVisual null or non-visual key/object mapping
* @throws IllegalArgumentException if pElement is null, or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final Element pElement, final Locale pLocale, final Map pNonVisual) throws IllegalArgumentException
{
if (pElement==null)
throw createException("null element");
if (!ROOT_ELEMENT.equals(pElement.getName()))
throw createException("XML root element ist not "+ROOT_ELEMENT, pElement);
SwingXMLBuilder builder= new SwingXMLBuilder(pLocale);
BuilderDelegate delegate;
// create non-visual stuff, first add the external non visual objects
if (pNonVisual!=null)
{
// check if all keys are strings
for (final Object o : pNonVisual.keySet())
{
if (!(o instanceof String))
{
if (o == null)
throw createException("key of the pNonVisual Map is not a string but null",
pElement);
else
throw createException("key of the pNonVisual Map is not a string but a " +
o.getClass().getName(), pElement);
}
}
// merge
builder.mNameToNonVisual.putAll(pNonVisual);
}
Element nonVisualElement= pElement.getChild(NONVISUAL_ELEMENT);
if (nonVisualElement!=null)
{
delegate= new NonVisualBuilderDelegate();
delegate.build(builder, nonVisualElement);
}
// create visual stuff second so we are sure that all actions are parsed
Element visualElement= pElement.getChild(VISUAL_ELEMENT);
if (visualElement!=null)
{
delegate= new VisualBuilderDelegate();
delegate.build(builder, visualElement);
}
// handle connnections
Element connectElement = pElement.getChild(CONNECT_ELEMENT);
if (connectElement!=null)
{
delegate= new ConnectionBuilderDelegate();
delegate.build(builder, connectElement);
}
// handle ValueModels
Element dataBindingElement= pElement.getChild(DATABINDING_ELEMENT);
if (dataBindingElement!=null)
{
delegate= new DataBindingBuilderDelegate();
delegate.build(builder, dataBindingElement);
}
// handle states
Element stateElement= pElement.getChild(STATEHANDLING_ELEMENT);
if (stateElement!=null)
{
delegate= new StateHandlingBuilderDelegate();
delegate.build(builder, stateElement);
}
return builder;
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pInputStream the XML description
* @throws IllegalArgumentException if there are problems with pInputStream,
* or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final InputStream pInputStream) throws IllegalArgumentException
{
return create(pInputStream, null, null);
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pInputStream the XML description
* @param pLocale locale to use or null
if the current default locale is to be used
* @throws IllegalArgumentException if there are problems with pInputStream,
* or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final InputStream pInputStream, final Locale pLocale) throws IllegalArgumentException
{
return create(pInputStream, pLocale, null);
}
/**
* Create visual, non-visual objects and a connection between them.
*
* @param pInputStream the XML description
* @param pNonVisual null or non-visual key/object mapping
* @throws IllegalArgumentException if there are problems with pInputStream,
* or the XML file contains errors
* @return a SwingXMLBuilder
*/
public static SwingXMLBuilder create(final InputStream pInputStream, final Map pNonVisual) throws IllegalArgumentException
{
return create(pInputStream, null, pNonVisual);
}
/**
* Create visual, non-visual objects and a connection between them.
* @param pInputStream the XML description
* @param pLocale locale to use or null
if the current
* default locale is to be used
* @param pNonVisual null or non-visual key/object mapping
* @return a SwingXMLBuilder
* @throws IllegalArgumentException if there are problems with pInputStream,
* or the XML file contains errors
*/
public static SwingXMLBuilder create(final InputStream pInputStream, final Locale pLocale, final Map pNonVisual) throws IllegalArgumentException
{
if (pInputStream==null)
throw createException("null inputstream");
SAXBuilder builder= new SAXBuilder();
builder.setEntityResolver(new EntityHelper());
if (XML_VALIDATE)
{
builder.setValidation(true);
}
try
{
Document doc = builder.build(pInputStream);
Element root= doc.getRootElement();
return create(root, pLocale, pNonVisual);
}
catch (Exception e)
{
throw createException("could not parse document", e);
}
}
/**
* This will cause all internal memory to be cleared, useful to
* prevent GC problems. We also dispose all known State's and ValueModel's
* Warning: no other method can be called after calling dispose!
*/
public void dispose()
{
if (!mIsDisposed)
{
for (final Object o1 : mNameToNonVisual.values())
{
if (o1 instanceof State)
{
State state = (State) o1;
if (!state.isDisposed())
{
state.dispose();
}
}
else if (o1 instanceof ValueModel>)
{
ValueModel> valueModel = (ValueModel>) o1;
if (!valueModel.isDisposed())
{
valueModel.dispose();
}
}
}
mIconCache= null;
mLocale= null;
mNameToNonVisual.clear();
mNameToNonVisual= null;
mNameToVisual.clear();
mNameToVisual= null;
mShortNameToVisual.clear();
mShortNameToVisual= null;
mSwingDecorator= null;
mSwingDecoratorCreated= false;
mVisualToName.clear();
mVisualToName= null;
mIsDisposed= true;
}
}
/*
* bulk access stuff of our known artefacts
*/
/**
* Return the hierarchical name to visual mapping.
* @return the name (key) to visual (value) map
*/
public Map getNameToVisual()
{
return mNameToVisual;
}
/**
* Return the short name to visual mapping.
* @return the name (key) to visual (value) map
*/
public Map getShortNameToVisual()
{
return mShortNameToVisual;
}
/**
* Return the visual to hierarchical name mapping.
* @return the visual (key) to the hierarchical name (value) map
*/
public Map getVisualToName()
{
return mVisualToName;
}
/**
* Return the name to non-visual mapping.
* @return the name (key) to non-visual (value) map
*/
public Map getNameToNonVisual()
{
return mNameToNonVisual;
}
/*
* single access stuff
*/
/**
* Returns a widget for the given name.
*
* @param pName (hierarchical) name of the widget
* @return null if the object is unknown
*/
public Container getContainerByName(final String pName)
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
Object value= getVisualByName(pName);
if (value instanceof Container)
return (Container)value;
else
return null;
}
/**
* Returns (hierarchical) name for the given widget.
*
* @param pContainer a widget
* @return null if the widget is unknown, (hierarchical) name otherwise
*/
public String getNameByContainer(final Container pContainer)
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
return mVisualToName.get(pContainer);
}
/**
* Returns a widget for the given name, shortcut for getComponentByName
*
* @param pName (hierarchical) name of the widget
* @return null if the object is unknown, or the widget is not a JComponent
* @throws IllegalArgumentException the name is not resolvable due too many ".."'s
*/
public JComponent get(final String pName)
{
return getComponentByName(pName);
}
/**
* Returns a widget for the given short name. If there is more than
* one component with that short name, it is undefined which is return.
* @param pName short name of the widget
* @return null if the object is unknown, or the widget is not a JComponent
*/
public JComponent getByShortName(final String pName)
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
Object value= mShortNameToVisual.get(pName);
if (value instanceof JComponent)
return (JComponent)value;
else
return null;
}
/**
* Returns a widget for the given name.
* @param pName (hierarchical) name of the widget
* @return null if the object is unknown, or the widget is not a JComponent
* @throws IllegalArgumentException the name is not resolvable due too many ".."'s
*/
public JComponent getComponentByName(final String pName)
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
Object value= getVisualByName(pName);
if (value instanceof JComponent)
return (JComponent)value;
else
return null;
}
/**
* Returns a widget by name. First the hierarchical name is
* tried, than the short name
* @param pName the hierarchical or shor name
* @return null if the object is unknown, or the widget is not a JComponent
* @throws IllegalArgumentException the name is not resolvable due too many ".."'s
*/
public JComponent getComponentByAnyName(final String pName)
{
JComponent back= getComponentByName(pName);
if (back==null)
{
back= getByShortName(pName);
}
return back;
}
/**
* common part of getContainerByName() and getComponentByName(), does also
* the ".." resolving
* @param pName component name (may be null), inside the
* name all ".."'s are resolved first, so it is o.k. to
* use "somePanel/someButton/../someTextField" to get
* from any component to any other.
* @return null or the object from mNameToVisual that matches the (resolved) name
*/
private Object getVisualByName(final String pName)
{
// null maps always to null
if (pName==null)
{
return null;
}
String name;
// check if we need the ".." resolving at all
if (!pName.contains(".."))
{
name= pName;
}
else
{
// the following code does the ".." resolving
StringTokenizer tokenizer = new StringTokenizer(pName, SEPARATOR);
List list = new ArrayList<>(tokenizer.countTokens());
int stackCount= 0;
while (tokenizer.hasMoreTokens())
{
String token= tokenizer.nextToken();
if ("..".equals(token))
{
// pop
stackCount--;
if (stackCount<0)
{
throw createException(pName + ": contains more ..'s than elements");
}
list.remove(stackCount);
}
else
{
// push
list.add(token);
stackCount++;
}
}
StringBuilder nameBuffer = new StringBuilder();
for (int i= 0, n= list.size(); i < n; i++)
{
nameBuffer.append(list.get(i));
if (i < n-1)
{
nameBuffer.append(SEPARATOR);
}
}
name= nameBuffer.toString();
}
// name now contains the resolved name, the Map will either return
// null or the matching object
return mNameToVisual.get(name);
}
/**
* Returns (hierarchical) name for the given widget.
*
* @param pComponent a widget
* @return null if the widget is unknown, (hierarchical) name otherwise
*/
public String getNameByComponent(final JComponent pComponent)
{
return getNameByContainer(pComponent);
}
/**
* Returns a non-visual object that was in the description.
*
* @param pName (hierarchical) name of the non-visual object, may be null
* @return null if the object is unknown
*/
public Object getNonVisualObject(final String pName)
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
return mNameToNonVisual.get(pName);
}
/**
* Returns a non-visual object that was in the description.
*
* @param pName (hierarchical) name of the non-visual object, may be null
* @param pType the type we should return, must not be null
* @param the type we expect
* @return null if the object is unknown
*/
public T getNonVisualObject(final String pName, Class pType)
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
return pType.cast(mNameToNonVisual.get(pName));
}
/**
* Return the icon cache of this builder.
* @return the icon cache of this builder
*/
public IconCache getIconCache()
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
return mIconCache;
}
/**
* Returns the widget factory of this builder. There is no setWidgetFactory()
* method, because it makes little sense to change the widget factory after
* all widgets are created.
* @return the widget factory of this builder
*/
public WidgetFactory getWidgetFactory()
{
if (mIsDisposed) throw new IllegalStateException("SwingXMLBuilder was disposed");
return getWidgetFactoryRegistry();
}
/**
* Small helper method to provide better exception handling by formatting
* the message.
*
* @param pMessagePrefix null or the initial part of the message
* @return IllegalArgumentException with a message and e as its cause
*/
public static IllegalArgumentException createException(final String pMessagePrefix)
{
return createException(pMessagePrefix, null, null);
}
/**
* Small helper method to provide better exception handling by formatting
* a message that includes the original exception.
*
* @param pException null or the exception that triggered the call
* @param pMessagePrefix null or the initial part of the message
* @return IllegalArgumentException with a message and e as its cause
*/
public static IllegalArgumentException createException(
final String pMessagePrefix, final Exception pException)
{
return createException(pMessagePrefix, pException, null);
}
/**
* Small helper method to provide better exception handling by formatting
* a message that includes the place where the error happend.
*
* @param pMessagePrefix null or the initial part of the message
* @param pElement null or the current element, the element's path will be appended
* to the message.
* @return IllegalArgumentException with a message and e as its cause
*/
public static IllegalArgumentException createException(
final String pMessagePrefix, final Element pElement)
{
return createException(pMessagePrefix, null, pElement);
}
/**
* Small helper method to provide better exception handling by formatting
* a message that includes the original exception and the
* place where the error happend.
*
* @param pMessagePrefix null or the initial part of the message
* @param pException null or the exception that triggered the call
* @param pElement null or the current element, the element's path will be appended
* to the message.
* @return IllegalArgumentException with a message and e as its cause
*/
public static IllegalArgumentException createException(
final String pMessagePrefix, final Exception pException,
final Element pElement)
{
String CR = System.getProperty("line.separator");
int INDENT = 4;
String prefix = (pMessagePrefix==null) ? "no detailed message" : pMessagePrefix;
StringBuilder sb = new StringBuilder(prefix);
if (pElement!=null)
{
// handle pElement
List elementBacktrace= new ArrayList<>();
Parent current = pElement;
do
{
elementBacktrace.add(current);
}
while ((current= current.getParent()) instanceof Element);
sb.append(", XML Element backtrace:");
sb.append(CR);
for (int i = 0, n = elementBacktrace.size(); i < n; i++)
{
Element element= (Element) elementBacktrace.get(n-i-1);
String id = element.getAttributeValue(ID_ATTRIBUTE);
for (int j= 0; j< i*INDENT; j++)
sb.append(' ');
sb.append(element.getName());
if (id!=null)
{
sb.append(" (id=");
sb.append(id);
sb.append(')');
}
else
{
sb.append(" (id unknown)");
}
if (i!=(n-1))
sb.append(CR);
}
}
IllegalArgumentException e= new IllegalArgumentException(sb.toString());
if (pException!=null)
{
e.initCause(pException);
}
return e;
}
/*
* helper stuff
*/
/**
* Tests if the element contains information that matches the language of the
* current locale.
* @param pElement element to check
* @param pLocale the locale
* @return true if the element has no special language or language matches
*/
public static boolean isSameLanguage(final Element pElement, final Locale pLocale)
{
String iso639Language= pLocale.getLanguage();
Attribute elementLanguage= pElement.getAttribute(LANG_ATTRIBUTE);
return ((elementLanguage==null) || elementLanguage.getValue().equals(iso639Language));
}
/**
* Tests if the element contains information that matches the language of the
* current locale.
* @param pElement element to check
* @return true if the element has no special language or language matches
*/
public boolean isSameLanguage(final Element pElement)
{
return isSameLanguage(pElement, mLocale);
}
/**
* Returns the text of last title child of the handed element in
* the current language or null, if no title child was found.
* @param pElement the element we start searching from
* @return null or the text of the title element
*/
public String getTitle(final Element pElement)
{
String title= null;
for (final Object o : pElement.getChildren(TITLE_ELEMENT))
{
Element titleElement = (Element) o;
if (isSameLanguage(titleElement))
{
title = titleElement.getText();
}
}
return title;
}
/**
* Returns the text of last title child of the handed element in
* the current language or null, if no title child was found.
* @param pElement the element we start searching from
* @return null or the text of the title element
*/
public static String getTitleDefaultLocale(final Element pElement)
{
String title= null;
for (final Object o : pElement.getChildren(TITLE_ELEMENT))
{
Element titleElement = (Element) o;
if (isSameLanguage(titleElement, Locale.getDefault()))
{
title = titleElement.getText();
}
}
return title;
}
/**
* Get a Class object from the class attribute from pElement.
* @param pElement element describing the object
* @return null or the Class object
* @throws IllegalArgumentException if we have a problem (parameters, ...)
*/
static Class> getClass(final Element pElement)
{
return getClass(pElement,CLASS_ATTRIBUTE);
}
/**
* Get a Class object from the class attribute from pElement.
* @param pElement element describing the object
* @param pAttributeName class attribute we are searching for
* @return null or the Class object
* @throws IllegalArgumentException if we have a problem (parameters, ...)
*/
static Class> getClass(final Element pElement, final String pAttributeName)
{
String sourceClassName = pElement.getAttributeValue(pAttributeName);
Class> sourceClass = null;
try
{
if (sourceClassName!=null)
{
sourceClass = Class.forName(sourceClassName);
}
}
catch (ClassNotFoundException e)
{
throw createException("class "+sourceClassName+" not found", e, pElement);
}
return sourceClass;
}
/**
* helper class to resolve our dtd via a local file
*/
private static class EntityHelper implements EntityResolver
{
/**
* callback method from the XML parser to resolve public/system ID's
* @param pPublicId not used
* @param pSystemId checked against our SYSTEM_ID member
* @return null or a InputSource for pSystemId
*/
public InputSource resolveEntity (final String pPublicId, final String pSystemId)
{
switch (pSystemId)
{
case SYSTEM_ID_2_1:
return loadDtd(SYSTEM_ID_2_1_FILE);
case SYSTEM_ID_2_0:
return loadDtd(SYSTEM_ID_2_0_FILE);
case SYSTEM_ID_1_9:
return loadDtd(SYSTEM_ID_1_9_FILE);
case SYSTEM_ID_1_8:
return loadDtd(SYSTEM_ID_1_8_FILE);
case SYSTEM_ID_1_7:
return loadDtd(SYSTEM_ID_1_7_FILE);
case SYSTEM_ID_1_6:
return loadDtd(SYSTEM_ID_1_6_FILE);
case SYSTEM_ID_1_5:
return loadDtd(SYSTEM_ID_1_5_FILE);
case SYSTEM_ID_1_4:
return loadDtd(SYSTEM_ID_1_4_FILE);
}
// if not one of our special SYSTEMID's, use the default behaviour
return null;
}
/**
* Small helper to load a file
* @param pFileName the dtd file name
* @return null or the InputSource for the file
*/
private InputSource loadDtd(final String pFileName)
{
InputSource back = null;
ClassLoader loader= Thread.currentThread().getContextClassLoader();
if (loader!=null)
{
InputStream dtd= loader.getResourceAsStream(pFileName);
if (dtd!=null)
{
back= new InputSource(dtd);
}
}
return back;
}
}
/**
* Helper interface that classes can implement to signal that the
* SwingXMLBuilder that created objects of that class will set a back link.
*/
public interface Backlink
{
/**
* Callback method for the Builder.
* @param pBuilder the builder that created that object
*/
void setSwingXMLBuilder(SwingXMLBuilder pBuilder);
}
/**
* Helper interface that delegate builder classes must implement.
*/
public interface BuilderDelegate
{
/**
* Build the specific part of the delegate.
* @param pBuilder the builder that controls the creation process
* @param pElement the JDOM element the delegate should handle
*/
void build(SwingXMLBuilder pBuilder, Element pElement);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy