/*
* Division.java
*
* Version: $Revision: 3705 $
*
* Date: $Date: 2009-04-11 18:02:24 +0100 (Sat, 11 Apr 2009) $
*
* Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
* Institute of Technology. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the Hewlett-Packard Company nor the name of the
* Massachusetts Institute of Technology nor the names of their
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.dspace.app.xmlui.wing.element;
/**
* Class representing a Division, or the div element, in the XML UI schema.
*
* The div element represents a major section of content and can contain a wide
* variety of other elements to present that content to the user. It can contain
* TEI style paragraphs, tables, and lists, as well as references to artifact
* information stored in artifactMeta. The div element is also recursive,
* allowing it to be further divided into other divs.
*
* @author Scott Phillips
*/
import java.util.ArrayList;
import org.dspace.app.xmlui.wing.AttributeMap;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingConstants;
import org.dspace.app.xmlui.wing.WingContext;
import org.dspace.app.xmlui.wing.WingException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.NamespaceSupport;
public class Division extends AbstractWingElement implements StructuralElement, WingMergeableElement
{
/** The name of the division element */
public static final String E_DIVISION = "div";
/** The name of the interactive attribute */
public static final String A_INTERACTIVE = "interactive";
/** The name of the action attribute */
public static final String A_ACTION = "action";
/** The name of the method attribute */
public static final String A_METHOD = "method";
/** The name of the method attribute */
public static final String A_BEHAVIOR = "behavior";
/** The name of the continuation name attribute. */
public static final String A_BEHVIOR_SENSITIVE_FIELDS = "behaviorSensitiveFields";
/** The name of the pagination attribute */
public static final String A_PAGINATION = "pagination";
/** The name of the previous page attribute */
public static final String A_PREVIOUS_PAGE = "previousPage";
/** The name of the next page attribute */
public static final String A_NEXT_PAGE = "nextPage";
/** The name of the items total attribute */
public static final String A_ITEMS_TOTAL = "itemsTotal";
/** The name of the first item index attribute */
public static final String A_FIRST_ITEM_INDEX = "firstItemIndex";
/** The name of the last item index attribute */
public static final String A_LAST_ITEM_INDEX = "lastItemIndex";
/** The name of the current page attribute */
public static final String A_CURRENT_PAGE = "currentPage";
/** The name of the pages total attribute */
public static final String A_PAGES_TOTAL = "pagesTotal";
/** The name of the page url mask attribute */
public static final String A_PAGE_URL_MASK = "pageURLMask";
/** Determines weather this division is being merged */
private boolean merged = false;
/** The name assigned to this div */
private String name;
/** Is this division interactive if so then action & method must be defined */
private boolean interactive;
/** Where should the result of this interactive division be posted to? */
private String action;
/** What method should this interactive division use for posting the result? */
private String method;
/** Does this interactive division support the ajax behavior? */
private boolean behaviorAJAXenabled = false;
/** A list of fields which need to be handled specialy when using bhavior */
private String behaviorSensitiveFields;
/** Special rendering instructions */
private String rend;
/** The head, or label of this division */
private Head head;
/** A paragraph that contains hidden fields */
private Para hiddenFieldsPara;
/**
* The pagination type of this div, either simple or masked. If null then
* this div is not paginated
*/
private String paginationType;
/** Url to the previousPage. (used by simple pagination) */
private String previousPage;
/** URL to the nextPage. (used by simple pagination) */
private String nextPage;
/**
* How many items exist across all paginated divs. (used by both pagination
* types)
*/
private int itemsTotal;
/**
* The index of the first item inculded in this div. (used by both
* pagination types)
*/
private int firstItemIndex;
/**
* The index of the first item inculded in this div. (used by both
* pagination types)
*/
private int lastItemIndex;
/**
* The index the current page being displayed. (used by maksed pagination
* type)
*/
private int currentPage;
/**
* The total number of pages in the pagination set. (used by maksed
* pagination type)
*/
private int pagesTotal;
/** The pagination URL mask. (used by maksed pagination type) */
private String pageURLMask;
/** The possible interactive division methods: get,post, or multipart. */
public static final String METHOD_GET = "get";
public static final String METHOD_POST = "post";
public static final String METHOD_MULTIPART = "multipart";
/** The possible interactive division methods names collected into one array */
public static final String[] METHODS = { METHOD_GET, METHOD_POST,
METHOD_MULTIPART };
/** The possible pagination types: simple & masked */
public static final String PAGINATION_SIMPLE = "simple";
public static final String PAGINATION_MASKED = "masked";
/** The possible pagination division types collected into one array */
public static final String[] PAGINATION_TYPES = { PAGINATION_SIMPLE,
PAGINATION_MASKED };
/** All content of this container, items & lists */
private java.util.List<AbstractWingElement> contents = new ArrayList<AbstractWingElement>();
/**
* Construct a non interactive division.
*
* @param context
* (Required) The context this element is contained in, such as
* where to route SAX events and what i18n catalogue to use.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
*/
protected Division(WingContext context, String name, String rend)
throws WingException
{
super(context);
require(name, "The 'name' parameter is required for divisions.");
this.name = name;
this.rend = rend;
}
/**
* Construct an interactive division. Interactive divisions must be
* accompanied by both an action and method to determine how to process form
* data.
*
* The valid values for method may be found in the static variable METHODS.
*
* @param context
* (Required) The context this element is contained in, such as
* where to route SAX events and what i18n catalogue to use.
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param action
* (Required) The form action attribute determines where the form
* information should be sent for processing.
* @param method
* (Required) Accepted values are "get", "post", and "multipart".
* Determines the method used to pass gathered field values to
* the handler specified by the action attribute. The multipart
* method should be used if there are any file fields used within
* the division.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
*/
protected Division(WingContext context, String name, String action,
String method, String rend) throws WingException
{
super(context);
require(name, "The 'name' parameter is required for divisions.");
// Blank actions are okay:
// require(action,
// "The 'action' parameter is required for interactive divisions.");
require(method, "The 'method' parameter is required for divisions.");
restrict(
method,
METHODS,
"The 'method' parameter must be one of these values: 'get', 'post', or 'multipart'.");
this.name = name;
this.interactive = true;
this.action = action;
this.method = method;
this.rend = rend;
}
/**
* Enable AJAX behaviors on this interactive division.
*/
public void enableAJAX()
{
this.behaviorAJAXenabled = true;
}
/**
* Add to the list of behavior sensitive fields, these fields should be
* updated each time a request partial page is submitted.
*
* @param fieldName
* (Required) The name of a single field (with no spaces).
*/
public void addBehaviorSensitiveField(String fieldName) throws WingException
{
require(fieldName, "The fieldName parameter is required for the behaviorSensitiveFields attribute.");
if (this.behaviorSensitiveFields == null)
{
this.behaviorSensitiveFields = fieldName;
}
else
{
this.behaviorSensitiveFields += " " + fieldName;
}
}
/**
* Make this div paginated ( a div that spans multiple pages ) using the
* simple page paradigm.
*
* @param itemsTotal
* (Required) How many items exist accross all paginated divs.
* @param firstItemIndex
* (Required) The index of the first item included in this div.
* @param lastItemIndex
* (Required) The index of the last item included in this div.
* @param previousPage
* (May be null) The URL of the previous page of the div, if it
* exists.
* @param nextPage
* (May be null) The URL of the previous page of the div, if it
* exists.
*/
public void setSimplePagination(int itemsTotal, int firstItemIndex,
int lastItemIndex, String previousPage, String nextPage)
{
this.paginationType = PAGINATION_SIMPLE;
this.previousPage = previousPage;
this.nextPage = nextPage;
this.itemsTotal = itemsTotal;
this.firstItemIndex = firstItemIndex;
this.lastItemIndex = lastItemIndex;
}
/**
* Make this div paginated ( a div that spans multiple pages ) using the
* masked page paradigm.
*
* @param itemsTotal
* (Required) How many items exist accross all paginated divs.
* @param firstItemIndex
* (Required) The index of the first item included in this div.
* @param lastItemIndex
* (Required) The index of the last item included in this div.
* @param currentPage
* (Required) The index of the page currently displayed for this
* div.
* @param pagesTotal
* (Required) How many pages the paginated div spans.
* @param pageURLMask
* (Required) The mask of a url to a particular within the
* paginated set. The destination page's number should replace
* the {pageNum} string in the URL mask to generate a full URL to
* that page.
*/
public void setMaskedPagination(int itemsTotal, int firstItemIndex,
int lastItemIndex, int currentPage, int pagesTotal,
String pageURLMask)
{
this.paginationType = PAGINATION_MASKED;
this.itemsTotal = itemsTotal;
this.firstItemIndex = firstItemIndex;
this.lastItemIndex = lastItemIndex;
this.currentPage = currentPage;
this.pagesTotal = pagesTotal;
this.pageURLMask = pageURLMask;
}
/**
* Set the head element which is the label associated with this division.
*
* @param characters
* (May be null) Unprocessed characters to be included
*/
public Head setHead() throws WingException
{
this.head = new Head(context, null);
return head;
}
/**
* Set the head element which is the label associated with this division.
*
* @param characters
* (May be null) Unprocessed characters to be included
*/
public void setHead(String characters) throws WingException
{
Head head = this.setHead();
head.addContent(characters);
}
/**
* Set the head element which is the label associated with this division.
*
* @param message
* (Required) A key into the i18n catalogue for translation into
* the user's preferred language.
*/
public void setHead(Message message) throws WingException
{
Head head = this.setHead();
head.addContent(message);
}
/**
* Add a sub division for further logical grouping of content.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
* @return A new sub Division
*/
public Division addDivision(String name, String rend) throws WingException
{
Division division = new Division(context, name, rend);
contents.add(division);
return division;
}
/**
* Add a sub division for further logical grouping of content.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @return A new sub division
*/
public Division addDivision(String name) throws WingException
{
return this.addDivision(name, null);
}
/**
* Add an interactive sub division for further logical grouping of content.
*
* The valid values for method may be found in the static variable METHODS.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param action
* (Required) The form action attribute determines where the form
* information should be sent for processing.
* @param method
* (Required) Accepted values are "get", "post", and "multipart".
* Determines the method used to pass gathered field values to
* the handler specified by the action attribute. The multipart
* method should be used if there are any file fields used within
* the division.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
* @return A new interactive sub division
*/
public Division addInteractiveDivision(String name, String action,
String method, String rend) throws WingException
{
Division division = new Division(context, name, action, method, rend);
contents.add(division);
return division;
}
/**
* Add an interactive sub division for further logical grouping of content
* without specifing special rendering instructions.
*
* The valid values for method may be found in the static variable METHODS.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param action
* (Required) The form action attribute determines where the form
* information should be sent for processing.
* @param method
* (Required) Accepted values are "get", "post", and "multipart".
* Determines the method used to pass gathered field values to
* the handler specified by the action attribute. The multipart
* method should be used if there are any file fields used within
* the division.
* @return A new interactive sub division
*/
public Division addInteractiveDivision(String name, String action,
String method) throws WingException
{
return addInteractiveDivision(name, action, method, null);
}
/**
* Append a paragraph to the division
*
* @param name
* (May be null) a local identifier used to differentiate the
* element from its siblings.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
* @return A new paragraph.
*/
public Para addPara(String name, String rend) throws WingException
{
Para para = new Para(context, name, rend);
contents.add(para);
return para;
}
/**
* Append an unnamed paragraph to the division
*
* @return A new unnamed paragraph.
*/
public Para addPara() throws WingException
{
return addPara(null, null);
}
/**
* Append a paragraph to the division and set the content of the paragraph.
*
* @param characters
* (May be null) Untranslated character data to be included as
* the contents of this para.
*/
public void addPara(String characters) throws WingException
{
Para para = this.addPara();
para.addContent(characters);
}
/**
* Append a paragraph to the division and set the content of the paragraph.
*
* @param message
* (Required) Key to the i18n catalogue to translate the content
* into the language preferred by the user.
*/
public void addPara(Message message) throws WingException
{
Para para = this.addPara();
para.addContent(message);
}
/**
* append a list to the division.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
*
* @param type
* (May be null) an optional attribute to explicitly specify the
* type of list. In the absence of this attribute, the type of a
* list will be inferred from the presence and content of labels
* on its items. Accepted values are found at
* org.dspace.app.xmlui.xmltool.List.TYPES
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
*
* @return A new List
*/
public List addList(String name, String type, String rend)
throws WingException
{
List list = new List(context, name, type, rend);
contents.add(list);
return list;
}
/**
* Append a list to the division.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
*
* @param type
* (May be null) an optional attribute to explicitly specify the
* type of list. In the absence of this attribute, the type of a
* list will be inferred from the presence and content of labels
* on its items. Accepted values are found at
* org.dspace.app.xmlui.xmltool.List.TYPES
*
* @return A new List
*/
public List addList(String name, String type) throws WingException
{
return this.addList(name, type, null);
}
/**
* Append a list to the division. The list type will be inferred by the
* presence and contents of labels and items.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @return A new List
*/
public List addList(String name) throws WingException
{
return this.addList(name, null, null);
}
/**
* Append a table to the division. When creating a table the number of rows
* and columns contained in the table must be pre computed and provided
* here.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
*
* @param rows
* (Required) The number of rows in the table.
* @param cols
* (Required) The number of columns in the table.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
*
* @return A new table.
*/
public Table addTable(String name, int rows, int cols, String rend)
throws WingException
{
Table table = new Table(context, name, rows, cols, rend);
contents.add(table);
return table;
}
/**
* Append a table to the division. When creating a table the number of rows
* and columns contained in the table must be pre computed and provided
* here.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
*
* @param rows
* (Required) The number of rows in the table.
* @param cols
* (Required) The number of columns in the table.
*
* @return A new table.
*/
public Table addTable(String name, int rows, int cols) throws WingException
{
return this.addTable(name, rows, cols, null);
}
/**
* Add a reference set for metadata refrences.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param type
* (Required) The include type, see IncludeSet.TYPES
* @param orderBy
* (May be null) An statement of ordering within the include set.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
*/
public ReferenceSet addReferenceSet(String name, String type, String orderBy,
String rend) throws WingException
{
ReferenceSet referenceSet = new ReferenceSet(context, false, name, type, orderBy, rend);
contents.add(referenceSet);
return referenceSet;
}
/**
* Add a reference set for metadata refrences.
*
* @param name
* (Required) a local identifier used to differentiate the
* element from its siblings.
* @param type
* (Required) The include type, see IncludeSet.TYPES
* @param orderBy
* (May be null) An statement of ordering within the include set.
* @param rend
* (May be null) a rendering hint used to override the default
* display of the element.
*/
public ReferenceSet addReferenceSet(String name, String type)
throws WingException
{
return addReferenceSet(name, type, null, null);
}
/**
* Add a hidden field to the division, this is a common operation that is
* not directly supported by DRI. To create support for it a new paragraph
* will be created with the name "hidden-fields" and a render attribute of
* "hidden".
*
* @param name
* (Required) The hidden fields name.
* @return A new hidden field.
*/
public Hidden addHidden(String name) throws WingException
{
if (hiddenFieldsPara == null)
{
hiddenFieldsPara = addPara("hidden-fields","hidden");
}
return hiddenFieldsPara.addHidden(name);
}
/**
* Add a section of translated HTML to the DRI document. This will only handle
* simple transformations such as <p>,<b>,<i>,and <a> tags.
*
* Depending on the given HTML this may result in multiple paragraphs being
* opened and several bold tags being included.
*
* @param blankLines
* (Required) Treat blank lines as paragraphs delimiters.
* @param HTML
* (Required) The HTML content
*/
public void addSimpleHTMLFragment(boolean blankLines, String HTML) throws WingException
{
contents.add(new SimpleHTMLFragment(context,blankLines,HTML));
}
/**
* Determine if the given SAX event is the same division element. This method
* will compare interactiveness, and rendering.
*
* @param namespace
* The element's name space
* @param localName
* The local, unqualified, name for this element
* @param qName
* The qualified name for this element
* @param attributes
* The element's attributes
* @return True if this WingElement is equivalent to the given SAX Event.
*/
public boolean mergeEqual(String namespace, String localName, String qName,
Attributes attributes) throws SAXException, WingException
{
if (!WingConstants.DRI.URI.equals(namespace))
return false;
if (!E_DIVISION.equals(localName))
return false;
context.getLogger().debug("Merding a division");
String name = attributes.getValue(A_NAME);
String interactive = attributes.getValue(A_INTERACTIVE);
String action = attributes.getValue(A_ACTION);
String method = attributes.getValue(A_METHOD);
String render = attributes.getValue(A_RENDER);
String pagination = attributes.getValue(A_PAGINATION);
String behavior = attributes.getValue(A_BEHAVIOR);
context.getLogger().debug("Merging got the parameters name="+name+", interactive="+interactive+", action="+action+", method="+method+", render="+render+", pagination="+pagination);
// The name must be identical (but id's can differ)
if (!this.name.equals(name))
return false;
// Insure the render attributes are identical.
if (this.rend == null)
{
if (render != null)
return false;
}
else if (!this.rend.equals(render))
{
return false;
}
if (this.interactive)
{
// Insure all the interactive fields are identical.
if (!"yes".equals(interactive))
return false;
if (!this.action.equals(action))
return false;
if (!this.method.equals(method))
return false;
// For now lets just not merge divs that have behavior.
if (!(behavior == null || behavior.equals("")))
return false;
} else {
// Else, insure that it is also not interactive.
if (!(interactive == null || "no".equals(interactive)))
return false;
}
return true;
}
/**
* Merge the given sub-domain of metadata elements.
*
* @param namespace
* The element's name space
* @param localName
* The local, unqualified, name for this element *
* @param qName
* The qualified name for this element
* @param attributes
* The element's attributes
* @return The child element
*/
public WingMergeableElement mergeChild(String namespace, String localName,
String qName, Attributes attributes) throws SAXException,
WingException
{
WingMergeableElement found = null;
for (AbstractWingElement content : contents)
{
if (content instanceof WingMergeableElement)
{
WingMergeableElement candidate = (WingMergeableElement) content;
if (candidate.mergeEqual(namespace, localName, qName,
attributes))
found = candidate;
}
}
contents.remove(found);
return found;
}
/**
* Notify this element that it is being merged.
*
* @return The attributes for this merged element
*/
public Attributes merge(Attributes attributes) throws SAXException,
WingException
{
this.merged = true;
return attributes;
}
/**
* Translate this division to SAX
*
* @param contentHandler
* (Required) The registered contentHandler where SAX events
* should be routed too.
* @param lexicalHandler
* (Required) The registered lexicalHandler where lexical
* events (such as CDATA, DTD, etc) should be routed too.
* @param namespaces
* (Required) SAX Helper class to keep track of namespaces able
* to determine the correct prefix for a given namespace URI.
*/
public void toSAX(ContentHandler contentHandler, LexicalHandler lexicalHandler,
NamespaceSupport namespaces) throws SAXException
{
if (!merged)
{
AttributeMap divAttributes = new AttributeMap();
divAttributes.put(A_NAME, name);
divAttributes.put(A_ID, context.generateID(E_DIVISION, name));
if (interactive)
{
divAttributes.put(A_INTERACTIVE, "yes");
divAttributes.put(A_ACTION, action);
divAttributes.put(A_METHOD, method);
if (behaviorAJAXenabled)
{
divAttributes.put(A_BEHAVIOR,"ajax");
}
if (behaviorSensitiveFields != null)
{
divAttributes.put(A_BEHVIOR_SENSITIVE_FIELDS,behaviorSensitiveFields);
}
}
if (PAGINATION_SIMPLE.equals(paginationType))
{
divAttributes.put(A_PAGINATION, paginationType);
if (previousPage != null)
divAttributes.put(A_PREVIOUS_PAGE, previousPage);
if (nextPage != null)
divAttributes.put(A_NEXT_PAGE, nextPage);
divAttributes.put(A_ITEMS_TOTAL, itemsTotal);
divAttributes.put(A_FIRST_ITEM_INDEX, firstItemIndex);
divAttributes.put(A_LAST_ITEM_INDEX, lastItemIndex);
}
else if (PAGINATION_MASKED.equals(paginationType))
{
divAttributes.put(A_PAGINATION, paginationType);
divAttributes.put(A_ITEMS_TOTAL, itemsTotal);
divAttributes.put(A_FIRST_ITEM_INDEX, firstItemIndex);
divAttributes.put(A_LAST_ITEM_INDEX, lastItemIndex);
divAttributes.put(A_CURRENT_PAGE, currentPage);
divAttributes.put(A_PAGES_TOTAL, pagesTotal);
divAttributes.put(A_PAGE_URL_MASK, pageURLMask);
}
if (rend != null)
divAttributes.put(A_RENDER, rend);
startElement(contentHandler, namespaces, E_DIVISION, divAttributes);
if (head != null)
head.toSAX(contentHandler, lexicalHandler, namespaces);
}
for (AbstractWingElement content : contents)
{
content.toSAX(contentHandler, lexicalHandler, namespaces);
}
if (!merged) {
endElement(contentHandler, namespaces, E_DIVISION);
}
}
/**
* dispose
*/
public void dispose()
{
if (head != null)
head.dispose();
head = null;
for (AbstractWingElement content : contents)
content.dispose();
if (contents != null)
contents.clear();
contents = null;
super.dispose();
}
public void addWingElement(AbstractWingElement e){
contents.add(e);
}
}