All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.metawidget.pipeline.base.BasePipeline Maven / Gradle / Ivy

There is a newer version: 4.2
Show newest version
// Metawidget (licensed under LGPL)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package org.metawidget.pipeline.base;

import static org.metawidget.inspector.InspectionResultConstants.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.metawidget.inspectionresultprocessor.iface.DomInspectionResultProcessor;
import org.metawidget.inspectionresultprocessor.iface.InspectionResultProcessor;
import org.metawidget.inspectionresultprocessor.iface.InspectionResultProcessorException;
import org.metawidget.inspector.iface.DomInspector;
import org.metawidget.inspector.iface.Inspector;
import org.metawidget.layout.iface.AdvancedLayout;
import org.metawidget.layout.iface.Layout;
import org.metawidget.widgetbuilder.iface.AdvancedWidgetBuilder;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;
import org.metawidget.widgetprocessor.iface.AdvancedWidgetProcessor;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;
import org.metawidget.widgetprocessor.iface.WidgetProcessorException;

/**
 * Convenience implementation for implementing pipelines (see
 * http://metawidget.org/doc/reference/en/html/ch02.html)
 * 

* Use of BasePipeline when developing Metawidgets is entirely optional. * However, it provides a level of functionality and structure to the code which * most Metawidgets will benefit from. *

* Specifically, BasePipeline provides support for: *

    *
  • Inspectors, InspectionResultProcessors, WidgetBuilders, WidgetProcessors * and Layouts
  • *
  • single/compound widgets
  • *
  • stubs/stub attributes
  • *
  • read-only/active widgets
  • *
  • maximum inspection depth
  • *
* This base class abstracts the pipeline without enforcing which XML libraries * to use. Most subclasses will choose * org.metawidget.pipeline.w3c.W3CPipeline, which uses * org.w3c.dom. *

* BasePipeline is not Thread-safe. *

* Note: this class is located in org.metawidget.pipeline.base, as * opposed to just org.metawidget.pipeline, to make it easier to * integrate GWT (which is bad at ignoring sub-packages such as * org.metawidget.pipeline.w3c). * * @author Richard Kennard */ public abstract class BasePipeline { // // Private statics // private static final int DEFAULT_MAXIMUM_INSPECTION_DEPTH = 10; // // Private members // private boolean mReadOnly; private int mMaximumInspectionDepth = DEFAULT_MAXIMUM_INSPECTION_DEPTH; private boolean mNeedsConfiguring = true; private Inspector mInspector; private List> mInspectionResultProcessors; private WidgetBuilder mWidgetBuilder; private List> mWidgetProcessors; private Layout mLayout; // // Public methods // public void setReadOnly(boolean readOnly) { mReadOnly = readOnly; } public boolean isReadOnly() { return mReadOnly; } public int getMaximumInspectionDepth() { return mMaximumInspectionDepth; } /** * Sets the maximum depth of inspection. *

* Metawidget renders most non-primitve types by using nested Metawidgets. * This value limits the number of nestings. *

* This can be useful in detecing cyclic references. Although * BaseObjectInspector -derived Inspectors are capable of * detecting cyclic references, other Inspectors may not be. For example, * BaseXmlInspector-derived Inspectors cannot because they only * test types, not actual objects. * * @param maximumInspectionDepth * 0 for top-level only, 1 for 1 level deep etc. */ public void setMaximumInspectionDepth(int maximumInspectionDepth) { mMaximumInspectionDepth = maximumInspectionDepth; } public void setNeedsConfiguring() { mNeedsConfiguring = true; } /** * Configures the Metawidget, then sets a flag so that subsequent calls to * configureOnce do nothing. *

* The flag can be reset by calling setNeedsConfiguring. */ public void configureOnce() { if (!mNeedsConfiguring) { return; } mNeedsConfiguring = false; configure(); } public void setInspector(Inspector inspector) { mInspector = inspector; } public Inspector getInspector() { configureOnce(); return mInspector; } /** * Gets the List of InspectionResultProcessors. *

* This pipeline only references a single Inspector and single * WidgetBuilder. It relies on CompositeInspector and CompositeWidgetBuilder * to support multiples, which allows the combination algorithm itself to be * pluggable. *

* We use a List of InspectionResultProcessors, however, so as to be * consistent with WidgetProcessors. Note ordering of * InspectionResultProcessors is significant. */ public List> getInspectionResultProcessors() { configureOnce(); return mInspectionResultProcessors; } public void setInspectionResultProcessors( InspectionResultProcessor... inspectionResultProcessors) { if (inspectionResultProcessors == null) { mInspectionResultProcessors = null; } else { mInspectionResultProcessors = new ArrayList>( Arrays.asList(inspectionResultProcessors)); } } public void addInspectionResultProcessor( InspectionResultProcessor inspectionResultProcessor) { configureOnce(); if (mInspectionResultProcessors == null) { mInspectionResultProcessors = new ArrayList>(); } else if (mInspectionResultProcessors .contains(inspectionResultProcessor)) { throw InspectionResultProcessorException .newException("List of InspectionResultProcessors already contains " + inspectionResultProcessor.getClass()); } mInspectionResultProcessors.add(inspectionResultProcessor); } public void removeInspectionResultProcessor( InspectionResultProcessor inspectionResultProcessors) { configureOnce(); if (mInspectionResultProcessors == null) { return; } mInspectionResultProcessors.remove(inspectionResultProcessors); } public void setWidgetBuilder(WidgetBuilder widgetBuilder) { mWidgetBuilder = widgetBuilder; } public WidgetBuilder getWidgetBuilder() { configureOnce(); return mWidgetBuilder; } /** * Gets the List of WidgetProcessors. *

* This pipeline only references a single Inspector and single * WidgetBuilder. It relies on CompositeInspector and CompositeWidgetBuilder * to support multiples, which allows the combination algorithm itself to be * pluggable. *

* We cannot use this same approach for WidgetProcessors, however, because * we want to support event handling. We want to allow, for example: *

* * metawidget.addWidgetProcessor( new WidgetProcessor() {
* ...handle event...
* } *
*

* This mechanism cannot be delegated to a CompositeWidgetProcessor, because * WidgetProcessors must be immutable, and we want to allow event handlers * that are non-static anonymous inner classes. *

* There, we use a List of WidgetProcessors. Note ordering of * WidgetProcessors is significant. */ public List> getWidgetProcessors() { configureOnce(); return mWidgetProcessors; } public void setWidgetProcessors(WidgetProcessor... widgetProcessors) { if (widgetProcessors == null) { mWidgetProcessors = null; } else { mWidgetProcessors = new ArrayList>( Arrays.asList(widgetProcessors)); } } public void addWidgetProcessor(WidgetProcessor widgetProcessor) { configureOnce(); if (mWidgetProcessors == null) { mWidgetProcessors = new ArrayList>(); } else if (mWidgetProcessors.contains(widgetProcessor)) { throw WidgetProcessorException .newException("List of WidgetProcessors already contains " + widgetProcessor.getClass()); } mWidgetProcessors.add(widgetProcessor); } public void removeWidgetProcessor(WidgetProcessor widgetProcessor) { configureOnce(); if (mWidgetProcessors == null) { return; } mWidgetProcessors.remove(widgetProcessor); } public Layout getLayout() { configureOnce(); return mLayout; } /** * Set the Layout to use for the Metawidget. */ public void setLayout(Layout layout) { mLayout = layout; } /** * Inspect the given Object according to the given path, and return the * result as a String conforming to inspection-result-1.0.xsd. *

* This method mirrors the Inspector interface. Internally it * looks up the Inspector to use. It is a useful hook for subclasses wishing * to inspect different Objects using our same Inspector. *

* In addition, this method runs the InspectionResultProcessors. */ public String inspect(Object toInspect, String type, String... names) { E element = inspectAsDom(toInspect, type, names); return elementToString(element); } /** * Inspect the given Object according to the given path, and return the * result as a String conforming to inspection-result-1.0.xsd. *

* This method mirrors the DomInspector interface. Internally * it looks up the Inspector to use. It is a useful hook for subclasses * wishing to inspect different Objects using our same * Inspector. *

* In addition, this method runs the InspectionResultProcessors. */ public E inspectAsDom(Object toInspect, String type, String... names) { configureOnce(); if (mInspector == null) { throw new NullPointerException("No inspector configured"); } Object inspectionResult; if (mInspector instanceof DomInspector) { inspectionResult = ((DomInspector) mInspector).inspectAsDom( toInspect, type, names); } else { inspectionResult = mInspector.inspect(toInspect, type, names); } if (inspectionResult == null) { return null; } return processInspectionResult(inspectionResult, toInspect, type, names); } /** * Build widgets from the given XML inspection result. *

* Note: the BasePipeline expects the XML to be passed in * externally, rather than fetching it itself, because some XML inspections * may be asynchronous. */ public void buildWidgets(E inspectionResult) throws Exception { configureOnce(); startBuild(); if (inspectionResult != null) { // Build simple widget (from the top-level entity) E entity = getFirstChildElement(inspectionResult); // Sanity check String elementName = getElementName(entity); if (!ENTITY.equals(elementName)) { throw new Exception("Top-level element name should be " + ENTITY + ", not " + elementName); } E nextSiblingElement = getNextSiblingElement(entity); if (nextSiblingElement != null) { throw new Exception("Top-level " + ENTITY + " element has a sibling " + getElementName(nextSiblingElement) + " element"); } // Metawidget-wide read-only Map attributes = getAttributesAsMap(entity); if (isReadOnly()) { attributes.put(READ_ONLY, TRUE); } // Build top-level widget. // // This includes invoking all WidgetBuilders, such as // OverriddenWidgetBuilder. It is a // little counter-intuitive that there can ever be an override of // the top-level element. // However, if we go down the path that builds a single widget (eg. // doesn't invoke // buildCompoundWidget), then our child is at the same top-level as // us, and there are // some scenarios (like Java Server Faces POST backs) where we need // to re-identify that W widget = buildWidget(ENTITY, attributes); // If mWidgetBuilder.buildWidget returns null, try // buildCompoundWidget (from our child // elements) if (widget == null) { buildCompoundWidget(entity); } else { widget = processWidget(widget, ENTITY, attributes); if (widget != null) { layoutWidget(widget, ENTITY, attributes); } } } // Even if no inspectors match, we still call startBuild()/endBuild() // because: // // 1. you can use a Metawidget purely for layout, with no inspection // 2. it makes us behave better in visual builder tools when dropping // child widgets in endBuild(); } /** * Copies this pipeline's values into another pipeline. Useful for when a * Metawidget creates a nested Metawidget. *

* Special behaviour is: *

    *
  • the given pipeline has setReadOnly if the current pipeline has * setReadOnly or if the attributes map contains * READ_ONLY
  • *
  • the given pipeline is initialised with a maximumInspectionDepth of 1 * less than the current maximumInspectionDepth. This is so that, as nesting * continues, eventually the maximumInspectionDepth reaches zero
  • *
  • the given pipeline is initialised with the same Inspectors, * InspectionResultProcessors, WidgetBuilders, WidgetProcessors and Layouts * as the current pipeline. This is safe because they are all immutable
  • *
* * @param attributes * may be null */ public void initNestedPipeline(BasePipeline nestedPipeline, Map attributes) { nestedPipeline .setReadOnly(isReadOnly() || (attributes != null && TRUE.equals(attributes .get(READ_ONLY)))); nestedPipeline .setMaximumInspectionDepth(getMaximumInspectionDepth() - 1); // Inspectors, InspectionResultProcessors, WidgetBuilders, // WidgetProcessors and Layouts can // be shared because they are immutable. However note that the // InspectionResultProcessor and // WidgetProcessor Lists are defensively copied nestedPipeline.setInspector(getInspector()); nestedPipeline.setWidgetBuilder(getWidgetBuilder()); nestedPipeline.setLayout(getLayout()); if (mInspectionResultProcessors == null) { nestedPipeline.mInspectionResultProcessors = null; } else { nestedPipeline.mInspectionResultProcessors = new ArrayList>( mInspectionResultProcessors); } if (mWidgetProcessors == null) { nestedPipeline.mWidgetProcessors = null; } else { nestedPipeline.mWidgetProcessors = new ArrayList>( mWidgetProcessors); } } // // Protected methods // /** * Build a compound widget by iterating through children of the given * element, calling buildWidget and addWidget on * each. */ protected void buildCompoundWidget(E entity) throws Exception { E child = getFirstChildElement(entity); int loop = 0; while (child != null) { loop++; // Sanity check String elementName = getElementName(child); if (!PROPERTY.equals(elementName) && !ACTION.equals(elementName)) { throw new Exception("Child element #" + loop + " should be " + PROPERTY + " or " + ACTION + ", not " + elementName); } Map attributes = getAttributesAsMap(child); String childName = attributes.get(NAME); if (childName == null || "".equals(childName)) { throw new Exception("Child element #" + loop + " has no @" + NAME); } // Metawidget as a whole may have had setReadOnly( true ) // // Note: we cannot do this in WidgetBuilderUtils.isReadOnly because: // // 1) There is not a common Metawidget class that we can pass to // WidgetBuilderUtils in // order for it to call isReadOnly // 2) This way WidgetBuilders/Layouts etc don't have to worry about // checking 2 places // for readOnly-ness // // In addition, we are trying to keep the exact nature of the // 'readOnly' mechanism (i.e. // set on attribute, or set on overall Metawidget) out of the // WidgetBuilders/WidgetProcessors/Layouts. This is because not // everybody will need/want // a Metawidget-level 'setReadOnly' boolean forcedReadOnly = false; if (!TRUE.equals(attributes.get(READ_ONLY)) && isReadOnly()) { attributes.put(READ_ONLY, TRUE); forcedReadOnly = true; } try { W widget = buildWidget(elementName, attributes); if (widget == null) { if (mMaximumInspectionDepth <= 0) { continue; } // If setReadOnly( true ), remove our forced attribute so // the nestedMetawidget // can differentiate whether it was forced or in the // inspector XML if (forcedReadOnly) { attributes.remove(READ_ONLY); } widget = buildNestedMetawidget(attributes); } Map additionalAttributes = getAdditionalAttributes(widget); if (additionalAttributes != null) { attributes.putAll(additionalAttributes); } widget = processWidget(widget, elementName, attributes); // A WidgetProcessor could return null to cancel the widget if (widget == null) { continue; } layoutWidget(widget, elementName, attributes); } finally { child = getNextSiblingElement(child); } } } // // Protected abstract methods // protected abstract E stringToElement(String xml); /** * Serialize the given element to an XML String. * * @param element * the element to serialize. May be null. */ protected abstract String elementToString(E element); protected abstract E getFirstChildElement(E parent); protected abstract E getNextSiblingElement(E element); protected abstract String getElementName(E element); protected abstract Map getAttributesAsMap(E element); protected abstract void configure(); protected void startBuild() { M pipelineOwner = getPipelineOwner(); if (mWidgetBuilder instanceof AdvancedWidgetBuilder) { ((AdvancedWidgetBuilder) mWidgetBuilder) .onStartBuild(pipelineOwner); } if (mWidgetProcessors != null) { for (WidgetProcessor widgetProcessor : mWidgetProcessors) { if (widgetProcessor instanceof AdvancedWidgetProcessor) { ((AdvancedWidgetProcessor) widgetProcessor) .onStartBuild(pipelineOwner); } } } // (layout can be null if no path, in an IDE visual builder) if (mLayout instanceof AdvancedLayout) { AdvancedLayout advancedLayout = (AdvancedLayout) mLayout; advancedLayout.onStartBuild(pipelineOwner); advancedLayout.startContainerLayout(pipelineOwner, pipelineOwner); } } /** * @param inspectionResult * may be a String of XML, or an E, depending on whether the * Inspector was a DomInspector */ protected E processInspectionResult(Object inspectionResult, Object toInspect, String type, String... names) { Object inspectionResultToProcess = inspectionResult; if (mInspectionResultProcessors != null) { M pipelineOwner = getPipelineOwner(); for (InspectionResultProcessor inspectionResultProcessor : mInspectionResultProcessors) { if (inspectionResultProcessor instanceof DomInspectionResultProcessor) { if (inspectionResultToProcess instanceof String) { inspectionResultToProcess = stringToElement((String) inspectionResultToProcess); } @SuppressWarnings("unchecked") DomInspectionResultProcessor domInspectionResultProcessor = (DomInspectionResultProcessor) inspectionResultProcessor; @SuppressWarnings("unchecked") E inspectionResultToProcessElement = (E) inspectionResultToProcess; inspectionResultToProcess = domInspectionResultProcessor .processInspectionResultAsDom( inspectionResultToProcessElement, pipelineOwner, toInspect, type, names); } else { if (!(inspectionResultToProcess instanceof String)) { @SuppressWarnings("unchecked") E inspectionResultToProcessElement = (E) inspectionResultToProcess; inspectionResultToProcess = elementToString(inspectionResultToProcessElement); } inspectionResultToProcess = inspectionResultProcessor .processInspectionResult( (String) inspectionResultToProcess, pipelineOwner, toInspect, type, names); } // An InspectionResultProcessor could return null to cancel the // inspection if (inspectionResultToProcess == null) { return null; } } } if (inspectionResultToProcess instanceof String) { return stringToElement((String) inspectionResultToProcess); } @SuppressWarnings("unchecked") E processedInspectionResult = (E) inspectionResultToProcess; return processedInspectionResult; } /** * Returns additional attributes associated with the widget. *

* At the very least, this method should be implemented to support returning * additional attributes from stubs. * * @return the additional attributes. May be null */ protected abstract Map getAdditionalAttributes(W widget); protected W buildWidget(String elementName, Map attributes) { if (mWidgetBuilder == null) { return null; } return mWidgetBuilder.buildWidget(elementName, attributes, getPipelineOwner()); } /** * Process the built widget. */ protected W processWidget(W widget, String elementName, Map attributes) { W processedWidget = widget; if (mWidgetProcessors != null) { M pipelineOwner = getPipelineOwner(); for (WidgetProcessor widgetProcessor : mWidgetProcessors) { processedWidget = widgetProcessor .processWidget(processedWidget, elementName, attributes, pipelineOwner); // A WidgetProcessor could return null to cancel the widget if (processedWidget == null) { return null; } } } return processedWidget; } protected abstract M buildNestedMetawidget(Map attributes) throws Exception; protected abstract M getPipelineOwner(); /** * Lays out the built and processed widget. */ protected void layoutWidget(W widget, String elementName, Map attributes) { M pipelineOwner = getPipelineOwner(); mLayout.layoutWidget(widget, elementName, attributes, pipelineOwner, pipelineOwner); } protected void endBuild() { M pipelineOwner = getPipelineOwner(); // (layout can be null if no path, in an IDE visual builder) if (mLayout instanceof AdvancedLayout) { AdvancedLayout advancedLayout = (AdvancedLayout) mLayout; advancedLayout.endContainerLayout(pipelineOwner, pipelineOwner); advancedLayout.onEndBuild(pipelineOwner); } if (mWidgetProcessors != null) { for (WidgetProcessor widgetProcessor : mWidgetProcessors) { if (widgetProcessor instanceof AdvancedWidgetProcessor) { ((AdvancedWidgetProcessor) widgetProcessor) .onEndBuild(pipelineOwner); } } } if (mWidgetBuilder instanceof AdvancedWidgetBuilder) { ((AdvancedWidgetBuilder) mWidgetBuilder) .onEndBuild(pipelineOwner); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy