/* File: FormBase.java * Copyright (C) 2002-2003 The University of Iowa * Created by: Jeremy Faden * Jessica Swanner * Edward E. West * * This file is part of the das2 library. * * das2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.das2.dasml; import org.das2.DasApplication; import org.das2.beans.BeansUtil; import org.das2.datum.Datum; import org.das2.util.DasExceptionHandler; //import org.apache.xml.serialize.*; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.swing.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.*; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.MethodDescriptor; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.*; import java.util.List; import java.util.logging.Logger; import org.das2.DasException; import org.das2.datum.LoggerManager; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; /** This class displays a Java form that is generated from an XML Document that is provided as input. * * @author Edward West */ public class FormBase extends JTabbedPane implements FormComponent { private static final Logger logger= LoggerManager.getLogger("das2.dasml"); /** The factory object used for creating DOM parsers. */ private static DocumentBuilderFactory domFactory; private DasApplication application = DasApplication.getDefaultApplication(); /** static initialization block for this class */ static { domFactory = DocumentBuilderFactory.newInstance(); URL schemaLocation = FormBase.class.getResource("schema/dasML.xsd"); if (schemaLocation != null) { domFactory.setAttribute( "http://apache.org/xml/features/validation/schema-full-checking", Boolean.TRUE); domFactory.setAttribute( "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", schemaLocation.toExternalForm()); domFactory.setValidating(true); domFactory.setNamespaceAware(true); } else { //TODO: Report this error, also maybe try to recover. } domFactory.setCoalescing(true); domFactory.setIgnoringElementContentWhitespace(true); } /**Initialization commands*/ private CommandBlock initBlock; List windowList = new ArrayList(); private JDesktopPane desktop; boolean editable; private boolean onHover = false; /** Setup for DnD support */ { DnDSupport dndSupport = new DnDSupport(); DropTarget dropTarget = new DropTarget(this, dndSupport); setDropTarget(dropTarget); } /** Creates a FormBase object * * @param url A uniform resouce locator pointing to the XML document to parse. */ public FormBase(URL url, ErrorHandler eh, boolean editable) throws IOException, SAXException { this(url.openStream(), eh, editable); } public FormBase(InputStream in, ErrorHandler eh, boolean editable) throws IOException, SAXException { this(new InputStreamReader(in), eh, editable); } public FormBase(Reader reader, ErrorHandler eh, boolean editable) throws IOException, SAXException { this.editable = editable; try { Document document = parseDasML(reader, eh); createFormFromTree(document); registerComponent(); } catch (ParserConfigurationException pce) { throw new IllegalStateException("DOM parser not configured properly: " + pce.getMessage()); } catch (org.das2.DasException de) { throw new SAXException(de); } } public FormBase(boolean editable) { this.editable = editable; } static Document parseDasML(Reader reader, ErrorHandler eh) throws ParserConfigurationException, SAXException, IOException { InputSource source = new InputSource(); source.setCharacterStream(reader); DocumentBuilder builder; synchronized (domFactory) { builder = domFactory.newDocumentBuilder(); } builder.setErrorHandler(eh); return builder.parse(source); } /** Sets up the Java form using a DOM document tree. * * @param doc The DOM document tree. */ private void createFormFromTree(Document doc) throws SAXException { try { Element das2 = doc.getDocumentElement(); if (!das2.getTagName().equals("das2")) ;//TODO: do some sort of error handling here. NodeList children = das2.getChildNodes(); int childCount = children.getLength(); for (int index = 0; index < childCount; index++) { Node node = children.item(index); if (node instanceof Element) { if (node.getNodeName().equals("form")) { FormTab form = new FormTab((Element)node, this); addForm(form); } else if (node.getNodeName().equals("window")) { FormWindow window = new FormWindow((Element)node, this); addWindow(window); } else if (node.getNodeName().equals("init")) { initBlock = processInitElement((Element)node); } else { //TODO: error message. } } else { //TODO: nothing, only interested in elements. } } } catch (DasException dne) { throw new SAXException(dne); } catch (ParsedExpressionException pee) { throw new SAXException(pee); } } private int getTabInsertionIndex() { int index = 0; int count = getComponentCount(); while (index < count && getComponent(index) instanceof FormTab) { index++; } return index; } public void addForm(FormTab form) { synchronized (getTreeLock()) { if (getEditingMode()) { int index = getTabInsertionIndex(); insertTab(form.getLabel(), null, form, null, index); } else { addTab(form.getLabel(), form); } } form.setEditingMode(getEditingMode()); } public void addWindow(FormWindow window) { if (window.form != null) { window.form.removeWindow(window); } window.form = this; windowList.add(window); boolean editingMode = getEditingMode(); window.setEditingMode(editingMode); if (editingMode) { if (desktop == null) { desktop = new JDesktopPane(); add(desktop, "Windows"); } window.pack(); desktop.add(window.getInternalFrame()); } this.firePropertyChange("window", null, window); } public void removeWindow(FormWindow window) { if (windowList.contains(window)) { windowList.remove(window); if (getEditingMode()) { desktop.remove(SwingUtilities.getAncestorOfClass(JInternalFrame.class, window)); } firePropertyChange("window", window, null); } } /** Process a <action> element. * * @param element The DOM tree node that represents the element */ private CommandBlock processActionElement(Element element) { return new CommandBlock(element, this); } /** Process a <glue> element. * * @param element The DOM tree node that represents the element */ Component processGlueElement(Element element) { String direction = element.getAttribute("direction"); if (direction.equals("horizontal")) return Box.createHorizontalGlue(); else return Box.createVerticalGlue(); } private CommandBlock processInitElement(Element element) throws SAXException { return new CommandBlock(element, this); } /** * Writes the XML representation of this form to the specified * byte stream * * @param out the specified byte stream */ public void serialize(OutputStream out) throws IOException { try { DocumentBuilder builder = domFactory.newDocumentBuilder(); Document document = builder.newDocument(); document.appendChild(getDOMElement(document)); DOMImplementationLS ls = (DOMImplementationLS) document.getImplementation().getFeature("LS", "3.0"); LSOutput output = ls.createLSOutput(); output.setEncoding("UTF-8"); output.setByteStream(out); ls.createLSSerializer().write(document, output); out.close(); /* String method = org.apache.xml.serialize.Method.XML; OutputFormat format = new OutputFormat(method, "UTF-8", true); format.setLineWidth(0); XMLSerializer serializer = new XMLSerializer(out, format); serializer.serialize(document); */ } catch (ParserConfigurationException pce) { IOException ioe = new IOException(pce.getMessage()); ioe.initCause(pce); throw ioe; } } private boolean isValidType(Class type) { return type.isPrimitive() || type == String.class || type == Datum.class || org.das2.datum.Datum.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type); } public Object checkValue(String name, Class type, String tag) throws org.das2.DasPropertyException, org.das2.DasNameException { try { Object obj = application.getNameContext().get(name); if (obj == null) { throw new org.das2.DasNameException(name + " must be defined before it is used"); } if (!type.isInstance(obj)) { throw new org.das2.DasPropertyException(org.das2.DasPropertyException.TYPE_MISMATCH, name, null); } return obj; } catch (InvocationTargetException ite) { throw new RuntimeException(ite); } } public Object invoke(String name, String[] args) throws org.das2.DasPropertyException, DataFormatException, ParsedExpressionException, InvocationTargetException { int lastDot = name.lastIndexOf('.'); if (lastDot == -1) throw new DataFormatException("No object associated with method name" + name); String objectName = name.substring(0, lastDot); String methodName = name.substring(lastDot+1); logger.fine("object name: " + objectName); logger.fine("method name: " + methodName); Object o = application.getNameContext().get(objectName); Method method = null; try { BeanInfo info = BeansUtil.getBeanInfo(o.getClass()); MethodDescriptor[] methodDescriptors = info.getMethodDescriptors(); for (int i = 0; i <= methodDescriptors.length; i++) { if (i == methodDescriptors.length) throw new org.das2.DasPropertyException(org.das2.DasPropertyException.NOT_DEFINED, methodName, objectName); if (!methodDescriptors[i].getName().equals(methodName)) continue; //if (methodDescriptors[i].getMethod().getParameterTypes().length != args.length) continue; method = methodDescriptors[i].getMethod(); break; } Class[] parameterTypes = method.getParameterTypes(); Object[] argValues = new Object[args.length]; for (int i = 0; i < parameterTypes.length; i++) { argValues[i] = Processor.parseValue(application.getNameContext(),args[i], parameterTypes[i]); } return method.invoke(o, argValues); } catch (IntrospectionException ie) { throw new DataFormatException(ie.getMessage()); } catch (InvocationTargetException ite) { throw new DataFormatException(ite.getTargetException().getMessage()); } catch (IllegalAccessException iae) { throw new DataFormatException(iae.getMessage()); } } public boolean getEditingMode() { return editable; } public void setEditingMode(boolean b) { if (editable != b) { editable = b; int componentCount = getComponentCount(); for (int i = 0; i < componentCount; i++) { Component c = getComponent(i); if (c instanceof FormTab) { ((FormTab)c).setEditingMode(b); } } if (windowList.size() > 0) { if (b) { if (desktop == null) { desktop = new JDesktopPane(); } add(desktop, "Windows"); } else { remove(desktop); } for (Iterator i = windowList.iterator(); i.hasNext();) { FormWindow window = (FormWindow)i.next(); window.setEditingMode(b); if (b) { window.pack(); JInternalFrame it = window.getInternalFrame(); if (it.getParent() != desktop) { desktop.add(it); } } } } revalidate(); repaint(); if (b) { firePropertyChange("editable", Boolean.FALSE, Boolean.TRUE); } else { firePropertyChange("editable", Boolean.TRUE, Boolean.FALSE); } } } public FormBase getForm() { return this; } public Element getDOMElement(Document document) { Element element = document.createElement("das2"); for (int index = 0; index < getComponentCount(); index++) { FormComponent child = (FormComponent)getComponent(index); element.appendChild(child.getDOMElement(document)); } if (!editable) { for (Iterator i = windowList.iterator(); i.hasNext();) { FormComponent child = (FormComponent)i.next(); element.appendChild(child.getDOMElement(document)); } } return element; } /** Paints the component's border. *

* If you override this in a subclass you should not make permanent * changes to the passed in Graphics. For example, you * should not alter the clip Rectangle or modify the * transform. If you need to do these operations you may find it * easier to create a new Graphics from the passed in * Graphics and manipulate it. * * @param g the Graphics context in which to paint * * @see #paint * @see #setBorder */ protected void paintBorder(Graphics g) { super.paintBorder(g); if (onHover) { Graphics2D g2 = (Graphics2D)g.create(); Stroke thick = new BasicStroke(3.0f); g2.setStroke(thick); g2.setColor(Color.GRAY); g2.drawRect(1, 1, getWidth() - 2, getHeight() - 2); g2.dispose(); } } public org.das2.util.DnDSupport getDnDSupport() { return null; } public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { return false; } public String getDasName() { return null; } public List getWindowList() { return Collections.unmodifiableList(windowList); } public void setDasName(String name) throws org.das2.DasNameException { throw new org.das2.DasNameException(); } public void deregisterComponent() { for (int index = 0; index < getComponentCount(); index++) { Component c = getComponent(index); if (c instanceof FormComponent) { ((FormComponent)c).deregisterComponent(); } } for (Iterator i = windowList.iterator(); i.hasNext();) { FormWindow w = (FormWindow)i.next(); w.deregisterComponent(); } } public org.das2.DasApplication getDasApplication() { return application; } public void registerComponent() throws org.das2.DasException { for (int index = 0; index < getComponentCount(); index++) { Component c = getComponent(index); if (c instanceof FormComponent) { ((FormComponent)c).registerComponent(); } } for (Iterator i = windowList.iterator(); i.hasNext();) { FormWindow w = (FormWindow)i.next(); w.registerComponent(); } } private class DnDSupport implements DropTargetListener { private final Set acceptableFlavors = new HashSet(Arrays.asList(new DataFlavor[] { TransferableFormComponent.TAB_FLAVOR, TransferableFormComponent.WINDOW_FLAVOR })); /** Called while a drag operation is ongoing, when the mouse pointer enters * the operable part of the drop site for the DropTarget * registered with this listener. * * @param dtde the DropTargetDragEvent */ public void dragEnter(DropTargetDragEvent dtde) { if (canAccept(dtde.getCurrentDataFlavors())) { dtde.acceptDrag(dtde.getSourceActions()); onHover = true; repaint(); } } /** Called while a drag operation is ongoing, when the mouse pointer has * exited the operable part of the drop site for the * DropTarget registered with this listener. * * @param dte the DropTargetEvent */ public void dragExit(DropTargetEvent dte) { onHover = false; repaint(); } /** Called when a drag operation is ongoing, while the mouse pointer is still * over the operable part of the drop site for the DropTarget * registered with this listener. * * @param dtde the DropTargetDragEvent */ public void dragOver(DropTargetDragEvent dtde) { } /** Called when the drag operation has terminated with a drop on * the operable part of the drop site for the DropTarget * registered with this listener. *

* This method is responsible for undertaking * the transfer of the data associated with the * gesture. The DropTargetDropEvent * provides a means to obtain a Transferable * object that represents the data object(s) to * be transfered.

* From this method, the DropTargetListener * shall accept or reject the drop via the * acceptDrop(int dropAction) or rejectDrop() methods of the * DropTargetDropEvent parameter. *

* Subsequent to acceptDrop(), but not before, * DropTargetDropEvent's getTransferable() * method may be invoked, and data transfer may be * performed via the returned Transferable's * getTransferData() method. *

* At the completion of a drop, an implementation * of this method is required to signal the success/failure * of the drop by passing an appropriate * boolean to the DropTargetDropEvent's * dropComplete(boolean success) method. *

* Note: The data transfer should be completed before the call to the * DropTargetDropEvent's dropComplete(boolean success) method. * After that, a call to the getTransferData() method of the * Transferable returned by * DropTargetDropEvent.getTransferable() is guaranteed to * succeed only if the data transfer is local; that is, only if * DropTargetDropEvent.isLocalTransfer() returns * true. Otherwise, the behavior of the call is * implementation-dependent. *

* @param dtde the DropTargetDropEvent */ public void drop(DropTargetDropEvent dtde) { boolean success = false; if (canAccept(dtde.getCurrentDataFlavors())) { Transferable t = dtde.getTransferable(); if (t.isDataFlavorSupported(TransferableFormComponent.COMPONENT_FLAVOR)) { dtde.acceptDrop(dtde.getDropAction()); success = acceptComponent(t); } else if (t.isDataFlavorSupported(TransferableFormComponent.DASML_FRAGMENT_FLAVOR)) { dtde.acceptDrop(DnDConstants.ACTION_COPY); success = acceptFragment(t); } dtde.dropComplete(success); } else { dtde.rejectDrop(); } onHover = false; repaint(); } private boolean acceptFragment(Transferable t) { boolean success = false; try { String data = (String)t.getTransferData(TransferableFormComponent.DASML_FRAGMENT_FLAVOR); Document document = FormBase.parseDasML(new StringReader(data), null); Element root = document.getDocumentElement(); if (root.getTagName().equals("form")) { FormTab tab = new FormTab(root, FormBase.this); addForm(tab); success = true; revalidate(); } else if (root.getTagName().equals("window")) { FormWindow window = new FormWindow(root, FormBase.this); addWindow(window); success = true; revalidate(); } } catch (org.das2.dasml.ParsedExpressionException pee) { pee.printStackTrace(); } catch (org.das2.DasException de) { de.printStackTrace(); } catch (UnsupportedFlavorException ufe) { //Allow to fall through. //exception is handled by allowing success to remain false } catch (IOException ioe) { DasExceptionHandler.handle(ioe); //Allow to fall through. //exception is handled by allowing success to remain false } catch (ParserConfigurationException pce) { DasExceptionHandler.handle(pce); //Allow to fall through. //exception is handled by allowing success to remain false } catch (SAXException se) { DasExceptionHandler.handle(se); //Allow to fall through. //exception is handled by allowing success to remain false } return success; } private boolean acceptComponent(Transferable t) { boolean success = false; try { Component c = (Component)t.getTransferData(TransferableFormComponent.COMPONENT_FLAVOR); if (c instanceof FormTab) { addForm((FormTab)c); success = true; revalidate(); } else if (c instanceof FormWindow) { addWindow((FormWindow)c); success = true; revalidate(); } else { System.out.println(c); } } catch (UnsupportedFlavorException ufe) { //Allow to fall through. //exception is handled by allowing success to remain false } catch (IOException ioe) { //Allow to fall through. //exception is handled by allowing success to remain false } return success; } /** Called if the user has modified * the current drop gesture. *

* @param dtde the DropTargetDragEvent */ public void dropActionChanged(DropTargetDragEvent dtde) { if (onHover) { dtde.acceptDrag(dtde.getDropAction()); } } private boolean canAccept(DataFlavor[] flavors) { for (int i = 0; i < flavors.length; i++) { if (acceptableFlavors.contains(flavors[i])) { return true; } } return false; } } }