tds.itemrenderer.webcontrols.PageLayout.java Source code

Java tutorial

Introduction

Here is the source code for tds.itemrenderer.webcontrols.PageLayout.java

Source

/*******************************************************************************
 * Educational Online Test Delivery System Copyright (c) 2014 American
 * Institutes for Research
 * 
 * Distributed under the AIR Open Source License, Version 1.0 See accompanying
 * file AIR-License-1_0.txt or at http://www.smarterapp.org/documents/
 * American_Institutes_for_Research_Open_Source_Software_License.pdf
 ******************************************************************************/
/**
 * 
 */
package tds.itemrenderer.webcontrols;

/**
 * @author Shiva BEHERA [sbehera@air.org]
 * 
 */
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.event.PhaseId;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import tds.itemrenderer.configuration.ITSConfig;
import tds.itemrenderer.configuration.RendererSettings;
import tds.itemrenderer.data.IITSDocument;
import tds.itemrenderer.data.IItemRender;
import tds.itemrenderer.data.ITSContent;
import tds.itemrenderer.data.ITSOption;
import tds.itemrenderer.data.ItemRenderGroup;
import tds.itemrenderer.data.ItemRenderMCOption;
import tds.itemrenderer.web.ITSDocumentJsonSerializable;
import tds.itemrenderer.webcontrols.PageSettings.UniqueIdType;
import tds.itemrenderer.webcontrols.templates.IResponseLayout;
import tds.itemrenderer.webcontrols.templates.ITemplateAnswer;
import tds.itemrenderer.webcontrols.templates.ITemplateAnswerTable;
import tds.itemrenderer.webcontrols.templates.ITemplateStem;
import tds.itemrenderer.webcontrols.templates.ITemplateStimulus;
import AIR.Common.Utilities.TDSStringUtils;
import AIR.Common.Web.BrowserParser;
import AIR.Common.Web.taglib.JsfHelpers;
import AIR.Common.Web.taglib.LiteralControl;
import AIR.Common.Web.taglib.PlaceHolder;

public class PageLayout extends UINamingContainer {
    private List<UserComponent> _externalComponents = new ArrayList<UserComponent>();
    // TODO Shiva this did not seem as it was used.
    // public event EventHandler OnRendered = delegate { };
    // private FacesContext _currentContext = null;
    private static Logger _logger = LoggerFactory.getLogger(PageLayout.class);
    private String _layoutFolder = ITSConfig.getLayoutFolder(); // e.x.,
                                                                // "~/Layouts_2009/"
    private String _responseTypesFolder = ITSConfig.getResponseFolder();

    private String _templateFolder = ITSConfig.getTemplateFolder();
    private String _templateFileStimulus = "Stimulus.xhtml";
    private String _templateFileTools = "Tools.xhtml";
    private String _templateFileStem = "Stem.xhtml";
    private String _templateFileIllustration = "Illustration.xhtml";
    private boolean _templateFileWAI = false;

    // these are calculated based on the content when rendering
    private String _layoutName = ""; // e.x.,
    // "11 - Vertical"
    private String _layoutFile; // e.x.,
                                // "Layout_11.ascx"
    private String _layoutLanguage;
    private String _responseTypeOverride = null;
    private PageSettings _settings = null;
    private ItemRenderGroup _itemGroup;
    private ErrorCategories _errorCategory = ErrorCategories.None;
    private String _errorDescription;
    private PlaceHolder _placeHolder;

    private PlaceHolder _contentPlaceHolder = new PlaceHolder();
    private PlaceHolder _preamblePlaceHolder = new PlaceHolder();
    private PlaceHolder _postamblePlaceHolder = new PlaceHolder();

    private String _renderToString;

    public PageLayout() {
        // set page settings defaults
        _settings = new PageSettings();
        _settings.setIncludePageWrapper(true);
        _settings.setIncludeItemWrapper(true);
        _settings.setUseUniquePageId(UniqueIdType.PageLayout);
        _settings.setIncludeJson(true);
    }

    public PageLayout(ItemRenderGroup renderGroup) {
        setItemRenderGroup(renderGroup);
    }

    public void renderUserControl(final String libraryName, final String resourceName) {
        _externalComponents.add(new UserComponent() {
            {
                _libraryName = libraryName;
                _resourceName = resourceName;
            }
        });
    }

    // / <summary>
    // / Override the layouts folder defined in the web.config for just this page.
    // / </summary>
    public String getLayoutFolder() {
        return _layoutFolder;
    }

    public void setLayoutFolder(String value) {
        _layoutFolder = value;
    }

    public PlaceHolder getPlaceHolder() {
        return _placeHolder;
    }

    public void setPlaceHolder(PlaceHolder value) {
        _placeHolder = value;

        _placeHolder.addComponent(_preamblePlaceHolder);
        _placeHolder.addComponent(_contentPlaceHolder);
        _placeHolder.addComponent(_postamblePlaceHolder);

        render();
    }

    // / <summary>
    // / The layout file name (not the full virtual path). If you set this field
    // yourself then you will
    // / override the items layout file which is determined from layout name.
    // / </summary>
    // / <example>"Layout_11.ascx"</example>
    public String getLayoutFile() {
        return _layoutFile;
    }

    public void setLayoutFile(String value) {
        _layoutFile = value;
    }

    // / <summary>
    // / This manually sets the layout name.
    // / </summary>
    // / <param name="layoutName"></param>
    // / <returns></returns>
    public boolean setLayout(String value) {
        _layoutName = value;
        return setLayout();
    }

    // / <summary>
    // / This calculates the layout name based on item information.
    // / </summary>
    public boolean setLayout() {
        // if there is no layout name manually assigned then get it from the first
        // item
        if (StringUtils.isEmpty(getLayoutName())) {
            if (getItemRenderGroup().size() > 0) {
                _layoutName = getItemRenderGroup().get(0).getItem().getLayout();
            } else if (getItemRenderGroup().getHasPassage()) {
                if (!StringUtils.isEmpty(getItemRenderGroup().getPassage().getLayout()))
                    _layoutName = getItemRenderGroup().getPassage().getLayout();
            }
        }

        // create the virtual path used to load the layout file
        if (StringUtils.isEmpty(getLayoutFile())) {
            if (StringUtils.isEmpty(getLayoutName()))
                return false;
            _layoutFile = "Layout_" + StringUtils.split(getLayoutName().replace(" ", ""), '-')[0] + ".xhtml";
        }

        return true;
    }

    // / <summary>
    // / The ITS layout name. If you set this field yourself then you will
    // override the items layout.
    // / </summary>
    // / <example>"11 - Vertical" or "11"</example>
    public String getLayoutName() {
        return _layoutName;
    }

    public void setLayoutName(String value) {
        _layoutName = value;
    }

    public String getLanguage() {
        return _layoutLanguage;
    }

    public String getResponseTypesFolder() {
        return _responseTypesFolder;
    }

    public void setResponseTypesFolder(String value) {
        _responseTypesFolder = value;
    }

    public String getTemplateFolder() {
        return _templateFolder;
    }

    public void setTemplateFolder(String value) {
        _templateFolder = value;
    }

    // / <summary>
    // / Set this response type to override the items response type.
    // / </summary>
    // / <example>Vertical</example>
    public String getResponseTypeOverride() {
        return _responseTypeOverride;
    }

    public void setResponseTypeOverride(String value) {
        _responseTypeOverride = value;
    }

    public PageSettings getSettings() {
        return _settings;
    }

    public void setItemRenderGroup(ItemRenderGroup value) {
        _itemGroup = value;
        setLanguage(value.getLanguage());
    }

    public ItemRenderGroup getItemRenderGroup() {
        return _itemGroup;
    }

    public ErrorCategories getErrorCategory() {
        return _errorCategory;
    }

    public String getErrorDescription() {
        return _errorDescription;
    }

    public String getRenderToString() {
        return _renderToString;
    }

    public void setRenderToString(String value) {
        _renderToString = value;
    }

    public void render() {
        try {
            renderControl();
        } catch (Exception ex) {
            _logger.error(ex.getMessage(), ex);
            throw ex;
        }
    }

    protected void setLanguage(String value) {
        _layoutLanguage = value;
    }

    protected void setSettings(PageSettings value) {
        _settings = value;
    }

    protected void setErrorCategory(ErrorCategories error) {
        this._errorCategory = error;
    }

    protected void setErrorDescription(String message) {
        this._errorDescription = message;
    }

    protected void renderControl() {
        if (_externalComponents.size() > 0) {
            for (UserComponent uc : _externalComponents) {
                JsfHelpers.<UIComponent, UIComponent>includeCompositeComponent(this.getPlaceHolder(),
                        uc._libraryName, uc._resourceName, null, null, null);
            }
        } else
            createChildControls2();
    }

    protected void createChildControls2() {

        ItemRenderGroup itemGroup = getItemRenderGroup();

        // check items for any possible rendering problems
        if (checkContentErrors())
            return;

        // check if we have any data to even render
        if (!itemGroup.getHasPassage() && !itemGroup.getHasItems()) {
            addError(ErrorCategories.Data,
                    "There was a problem rendering item(s) no content provided to renderer.");
            return;
        }

        // set the layout information for this content
        if (!setLayout()) {
            addError(ErrorCategories.Layout,
                    "Could not figure out the layout for this group. This could be because there are no items or it is missing from the first item.");
            return;
        }

        // check if we found a layout (if there are no items and no manually
        // assigned layout then this can occur)
        if (StringUtils.isEmpty(getLayoutFolder()) || StringUtils.isEmpty(getLayoutFile())) {
            addError(ErrorCategories.Layout, "There was no layout information found.");
            return;
        }

        ILayout layout = loadLayout(getLayoutFile(), _contentPlaceHolder);
        // TODO Shiva: We will never come here, I suppose. If there was an error
        // then we would already have
        // thrown an exception at this point. Is this still required?
        if (layout == null) {
            if (itemGroup.size() > 0) {
                addError(ErrorCategories.Layout, "The layout '{0}' did not load properly. Failed to load item {1}.",
                        getLayoutFile(), getItemRenderGroup().get(0).getItem().getItemKey());
            } else {
                addError(ErrorCategories.Layout, "The layout '{0}' did not load properly.", getLayoutFile());
            }
            return;
        }

        // TODO Shiva: is there any layout that is not an instance of renderer base
        // in .NET? Also our inheritance scheme makes the following if condition
        // redundant - it will
        // always be true.
        // check if the layout overrided any of the default template files
        if (layout instanceof RendererBase) {
            RendererBase rendererBase = (RendererBase) layout;

            if (!StringUtils.isEmpty(rendererBase.getTemplateStimulus())) {
                this._templateFileStimulus = rendererBase.getTemplateStimulus();
            }

            if (!StringUtils.isEmpty(rendererBase.getTemplateStem())) {
                this._templateFileStem = rendererBase.getTemplateStem();
            }

            if (!StringUtils.isEmpty(rendererBase.getTemplateIllustration())) {
                this._templateFileIllustration = rendererBase.getTemplateIllustration();
            }

            if (rendererBase.getTemplateWai() == null) {
                this._templateFileWAI = false;
            } else {
                this._templateFileWAI = rendererBase.getTemplateWai();
            }
        }

        // NOTE FOR IF DECIDE TO USE CACHING: We need to add the layout to the
        // control early (http://support.microsoft.com/kb/837000)
        // render passage
        if (itemGroup.getHasPassage()) {
            renderStimulus(layout, itemGroup.getPassage());
        }

        // render illustration
        else if (itemGroup.getHasItems()) {
            renderIllustration(layout, itemGroup.get(0));
        }

        // render items
        if (itemGroup.getHasItems()) {
            // set boolean indicating first item
            itemGroup.get(0).setIsFirst(true);

            // set boolean indicating last item
            itemGroup.get(itemGroup.size() - 1).setIsLast(true);

            // Assign itemGroup (items and passage data) to the layout,
            setRenderData(layout, itemGroup.get(0));

            // check for layout type and render items
            if (layout instanceof LayoutSingle) {
                if (itemGroup.size() > 1) {
                    addError(ErrorCategories.Layout,
                            "There was a problem using this single item layout '{0}' because there are multiple items trying to be rendered. The first item is {1}.",
                            _layoutFile, itemGroup.get(0).getItem().getItemKey());
                } else {
                    renderSingleItem((LayoutSingle) layout, itemGroup.get(0));
                }
            } else if (layout instanceof LayoutCompound) {
                renderCompoundItems((LayoutCompound) layout);
            } else if (layout instanceof LayoutMulti) {
                renderMultiItems((LayoutMulti) layout);
            } else if (layout instanceof LayoutColumns) {
                renderColumnItems((LayoutColumns) layout);
            } else if (layout instanceof LayoutSingleMulti) {
                renderSingleMultiItems((LayoutSingleMulti) layout);
            } else {
                addError(ErrorCategories.Layout,
                        "Unknown inherited type for layout '{0}', should be LayoutSingle or LayoutMulti. Failed to load item {1}.",
                        _layoutFile, itemGroup.get(0).getItem().getItemKey());
            }
        } else {
            setRenderData(layout);
        }

        // add page wrapper
        addPageWrapper(layout);
    }

    protected void renderColumnItems(LayoutColumns layoutColumns) {
        ItemRenderGroup itemGroup = getItemRenderGroup();

        int column1 = (itemGroup.size() / 2) + (itemGroup.size() % 2);

        // column 1
        for (int i = 0; i < column1; i++) {
            ILayout layoutSingle = loadLayout(layoutColumns.getColumn1().getLayout(), layoutColumns.getColumn1());
            setRenderData(layoutSingle, itemGroup.get(i));
        }

        // column 2
        for (int i = column1; i < _itemGroup.size(); i++) {
            ILayout layoutSingle = loadLayout(layoutColumns.getColumn2().getLayout(), layoutColumns.getColumn2());
            setRenderData(layoutSingle, itemGroup.get(i));
        }
    }

    // / <summary>
    // / This is used to render compound items. These are where we combine a
    // series of items to make it look like one item.
    // / </summary>
    // / <remarks>
    // / The code in this function is a combination of RenderSingleItem and
    // RenderMultiItems. In the future we need to make
    // / it so we can call on those functions directly to help us out. Right now
    // they require a specific class and we should
    // / switch to using interfaces instead.
    // / </remarks>
    protected void renderCompoundItems(LayoutCompound layout) {
        // TODO
    }

    protected void renderMultiItems(LayoutMulti layout1) {
        if (layout1.getQuestions() == null)
            return; // <tds:QuestionRepeater /> was not added to page

        List<Question> questions = new ArrayList<Question>();

        ItemRenderGroup itemGroup = getItemRenderGroup();
        // add stem to layout
        // int counter1 = 0;
        for (IItemRender item : itemGroup) {
            // Shiva: This is a slight variation from .NET. We create PlaceHolders
            // instead
            // to act as parent nodes.
            Question question = new Question();
            question.setItem(item);

            PlaceHolder illustrationParentPlaceHolder = new PlaceHolder();
            getTemplateIllustration(item, illustrationParentPlaceHolder);
            question.setIllustration(illustrationParentPlaceHolder);

            PlaceHolder stemParentPlaceHolder = new PlaceHolder();
            getTemplateStem(item, stemParentPlaceHolder);
            question.setStem(stemParentPlaceHolder);

            PlaceHolder responseParentPlaceHolder = new PlaceHolder();
            getResponseControl(item, responseParentPlaceHolder);
            question.setAnswer(responseParentPlaceHolder);

            questions.add(question);
        }

        layout1.getQuestions().setValue(questions);
        layout1.getQuestions().process(getFacesContext(), PhaseId.UPDATE_MODEL_VALUES);

    }

    protected void renderSingleItem(LayoutSingle layoutSingle, IItemRender itemRender) {
        if (itemRender == null) {
            _contentPlaceHolder.addComponent(new LiteralControl("[NO ITEM DATA]"));
            return;
        }

        // add stem to layout
        if (layoutSingle.getStem() != null) {
            getTemplateStem(itemRender, layoutSingle.getStem());
        }

        // add answer to layout
        if (layoutSingle.getAnswer() != null) {
            getResponseControl(itemRender, layoutSingle.getAnswer());
        }
    }

    // / <summary>
    // / Used to determine what response type template to load.
    // / </summary>
    protected UIComponent getResponseControl(IItemRender itemRender, UIComponent parent) {
        try {
            UIComponent control = null;

            String responseType = getResponseType(itemRender.getItem());

            // make sure we have a valid response type
            if (StringUtils.isEmpty(responseType)) {
                addError(ErrorCategories.Template,
                        "Could not find the response type for layout '{0}'. Failed to load item {1}.",
                        itemRender.getItem().getLayout(), itemRender.getItem().getItemKey());
                return null;
            }

            // ignore responses types that are defined as "Empty"
            if (StringUtils.equals(responseType.toUpperCase(), "NA"))
                return null;

            ITSContent content = getContent(itemRender.getItem());
            if (content == null)
                return null;

            // load the control based on response type name
            IResponseLayout responseLayout = getResponseControl(responseType.replace(" ", ""), parent);
            if (!(responseLayout instanceof UIComponent) || (responseLayout == null)) {
                addError(ErrorCategories.Template,
                        "Could not find the answer template for layout '{0}'. Failed to load item {1}.",
                        itemRender.getItem().getLayout(), itemRender.getItem().getItemKey());
                return null;
            }
            control = (UIComponent) responseLayout;

            // check if we can data bind the options to this item
            if (hasBindableOptions(itemRender.getItem().getFormat(), responseType)) {
                bindResponseControl_MC(itemRender, content, control);
            }

            setRenderData(control, itemRender);

            return control;
        } catch (Exception exp) {
            throw new RuntimeException(exp);
        }
    }

    @SuppressWarnings("unchecked")
    protected void bindResponseControl_MC(IItemRender itemRender, ITSContent itsContent, UIComponent control) {
        final char delimiter = ',';

        // logic for figuring our answer template
        List<ItemRenderMCOption> renderOptions = new ArrayList<ItemRenderMCOption>();

        // check if there are any MC options
        if (itsContent.getOptions() != null) {
            for (ITSOption option : itsContent.getOptions()) {
                ItemRenderMCOption renderMCOption = new ItemRenderMCOption(
                        "Response_MC_" + itemRender.getPosition(), option.getKey());
                renderMCOption.setText(option.getValue());
                renderMCOption.setFeedback(option.getFeedback());
                renderMCOption.setSound(option.getSound());
                renderMCOption.setDisabled(itemRender.getDisabled());

                // get the answer key
                String answerString = itemRender.getItem().getAnswerKey();

                // set the correct answer on the option
                if (!StringUtils.isEmpty(answerString)) {
                    String[] answerKeys = StringUtils.split(answerString, delimiter);

                    for (String answerKey : answerKeys) {
                        // check if matching answer key
                        if (StringUtils.equals(option.getKey(), answerKey)) {
                            renderMCOption.setAnswer(true);
                            break;
                        }
                    }
                }

                // check for a response
                if (!StringUtils.isEmpty(itemRender.getResponse())) {
                    // check if multiple response
                    if (StringUtils.equalsIgnoreCase(itemRender.getItem().getFormat(), "MS")) {
                        String[] responseKeys = StringUtils.split(itemRender.getResponse(), delimiter);

                        // go through all the response keys to see if they match option key
                        for (String responseKey : responseKeys) {
                            // check if matching response key
                            if (StringUtils.equals(option.getKey(), responseKey)) {
                                renderMCOption.setSelected(true);
                                break;
                            }
                        }
                    } else {
                        // check if response key matches option key
                        renderMCOption.setSelected(StringUtils.equals(option.getKey(), itemRender.getResponse()));
                    }
                }
                renderOptions.add(renderMCOption);
            }
        }

        // perform data binding
        if (control instanceof ITemplateAnswer) {
            ITemplateAnswer templateAnswer = (ITemplateAnswer) control;
            templateAnswer.setOptions(renderOptions);
        }
        // TODO Shiva: Finish the loops below
        /*
         * else if (control instanceof ITemplateAnswerMC) { ITemplateAnswerMC
         * templateAnswer = control as ITemplateAnswerMC;
         * templateAnswer.Options.DataSource = renderOptions;
         * templateAnswer.Options.DataBind(); }
         */
        else if (control instanceof ITemplateAnswerTable) {

            ITemplateAnswerTable templateAnswer = (ITemplateAnswerTable) control;
            templateAnswer.setOptions((List<Object>) CollectionUtils.collect(renderOptions, new Transformer() {
                @Override
                public Object transform(Object input) {
                    return input;
                }
            }));
        }
        /*
         * else if (control is ITemplateAnswerRepeater) { ITemplateAnswerRepeater
         * templateAnswer = control as ITemplateAnswerRepeater;
         * templateAnswer.Options.DataSource = renderOptions;
         * templateAnswer.Options.DataBind(); }
         */
        else {
            addError(ErrorCategories.Template,
                    "Cannot bind data to the answer template '{0}' because it inherits from unknown interface. Failed to load item {1}.",
                    control.getClientId(), itemRender.getItem().getItemKey());
        }
    }

    protected UIComponent getTemplateStem(IItemRender itemRender, UIComponent parent) {
        ITemplateStem templateStem = JsfHelpers.<ITemplateStem, ITemplateStem>includeCompositeComponent(parent,
                getTemplateFolder(), this._templateFileStem, null, null, null);

        ITSContent itsContent = getContent(itemRender.getItem());
        if (itsContent == null)
            return null;
        // IMAGE BASE
        templateStem.getQuestion().setText(itsContent.getStem());

        setRenderData(templateStem, itemRender);

        return templateStem;
    }

    // / <summary>
    // / Renders a single item on the left and all the other items on the right.
    // / </summary>
    // / <param name="layout"></param>
    // / <remarks>
    // / Since we made this change in the middle of the year this
    // / code contains copies of code from the functions:
    // / * RenderSingleItem()
    // / * RenderMultiItems()
    // / TODO: We need to reuse existing functions for 2012.
    // / </remarks>
    protected void renderSingleMultiItems(LayoutSingleMulti layout) {
        // TODO
    }

    protected void renderIllustration(ILayout layout, IItemRender itemRender) {
        if (layout.getStimulus() != null) {
            // add stimulus template to layout
            ITemplateStimulus templateStimulus = getTemplateIllustration(layout.getStimulus());

            if (templateStimulus == null) {
                addError(ErrorCategories.Template, "Could not load illustration template. Failed to load item {0}.",
                        itemRender.getItem().getItemKey());
                return;
            }

            ITSContent itsContent = getContent(itemRender.getItem());
            if (itsContent == null)
                return;

            String text = "";

            if (!StringUtils.isEmpty(itsContent.getIllustration())) {
                // IMAGE BASE
                text = itsContent.getIllustration();
            }

            templateStimulus.getContent().setText(text);
        }
    }

    protected ITSContent getContent(IITSDocument itsDoc) {
        // if language is not available use english
        ITSContent content = itsDoc.getContent(this.getLanguage()); // ??
                                                                    // itsDoc.GetContentDefault();
        if (content == null) {
            addError(ErrorCategories.Content,
                    "The <content language=\"{0}\"> element for {1} {2} ({3}) is empty or not parsed properly.",
                    this.getLanguage(), itsDoc.getType(), itsDoc.getItemKey(), itsDoc.getBankKey());
        }
        return content;
    }

    protected void renderStimulus(ILayout layout, IITSDocument itsDoc) {
        if (layout.getStimulus() == null)
            return;

        // get stimulus template and add it to layout component.
        ITemplateStimulus uc = JsfHelpers.<ITemplateStimulus, ITemplateStimulus>includeCompositeComponent(
                layout.getStimulus(), getTemplateFolder(), _templateFileStimulus, null, null, null);

        // TODO Shiva: What is PartialCachingControl? Do I need to implement it?
        /*
         * if (uc == null) { PartialCachingControl pcc = control as
         * PartialCachingControl;
         * 
         * if (pcc != null) { uc = pcc.CachedControl as ITemplateStimulus; } else {
         * //Debug.WriteLine("Passage cached"); } }
         */

        // check if stimulus control loaded/
        // TODO shiva: is this still required?
        if (uc == null)
            return;

        ITSContent itsContent = getContent(itsDoc);

        // check if stimulus content was found
        if (itsContent == null)
            return;

        // stimulus title
        if (uc.getTitle() != null && !StringUtils.isEmpty(itsContent.getTitle())) {
            uc.getTitle().setText(itsContent.getTitle());
        }

        // stimulus credit
        if (uc.getCredit() != null && !StringUtils.isEmpty(itsDoc.getCredit())) {
            uc.getCredit().setText(TDSStringUtils.format("<div class=\"attribute\">{0}</div>", itsDoc.getCredit()));
        }

        // stimulus text
        if (uc.getContent() != null && !StringUtils.isEmpty(itsContent.getStem())) {
            // IMAGE BASE
            uc.getContent().setText(itsContent.getStem());
        }
    }

    private void addHtml(String text) {
        _preamblePlaceHolder.addComponent(new LiteralControl("\r\n" + text));
    }

    private void addHtml(String text, PlaceHolder placeHolder) {
        placeHolder.addComponent(new LiteralControl("\r\n" + text));
    }

    private void addScript(String script) {
        addHtml("<script type=\"text/javascript\">", _postamblePlaceHolder);
        addHtml("//<![CDATA[", _postamblePlaceHolder);
        addHtml(script, _postamblePlaceHolder);
        addHtml("//]]>", _postamblePlaceHolder);
        addHtml("</script>", _postamblePlaceHolder);
    }

    private void addPageWrapper(ILayout layout) {
        IITSDocument rootDoc = _itemGroup.getHasPassage() ? _itemGroup.getPassage() : _itemGroup.get(0).getItem();

        String layoutID = _layoutName.replace("-", "_").replace(" ", "").toLowerCase();
        String subject = rootDoc.getSubject().replace(" ", "");
        String grade = rootDoc.getGrade().replace(" ", "");

        PageSettings settings = getSettings();

        if (settings.getIncludePageWrapper()) {

            // <div> (pageLayout wrapper info)
            String pageID = "pageLayout";
            if (UniqueIdType.GroupId == settings.getUseUniquePageId())
                pageID = "Page_" + _itemGroup.getId();
            else if (UniqueIdType.IRiSGuid == settings.getUseUniquePageId())
                pageID = _itemGroup.getId();

            BrowserParser browser = new BrowserParser();

            // create styles
            StringBuilder styles = new StringBuilder();
            if (!StringUtils.isEmpty(subject))
                styles.append(TDSStringUtils.format("subject_{0} ", subject));
            if (!StringUtils.isEmpty(grade))
                styles.append(TDSStringUtils.format("grade_{0} ", grade));
            styles.append(TDSStringUtils.format("itemcount_{0} ", _itemGroup.size()));
            styles.append(TDSStringUtils.format("layout_{0} ", layoutID));
            styles.append(TDSStringUtils.format("browser_{0} ", browser.getName().toLowerCase()));
            styles.append(TDSStringUtils.format("browserVer_{0} ", ("" + browser.getVersion()).replace('.', '_')));
            styles.append(TDSStringUtils.format("platform_{0}", browser.getOsName().toString().toLowerCase()));

            String div_page;

            if (RendererSettings.supportHtmlReplacements()) {
                div_page = "<div xmlns=\"http://www.w3.org/1999/xhtml\" id=\"{0}\" class=\"{1}\">";
            } else {
                div_page = "<div id=\"{0}\" class=\"{1}\">";
            }

            addHtml(TDSStringUtils.format(div_page, pageID, styles));
        }

        // <div> (itemContainer)
        // For single item layouts we wrap them in a div with item level information
        // (response type, format)
        if (settings.getIncludeItemWrapepr() && layout instanceof LayoutSingle && _itemGroup.getHasItems()) {
            IItemRender item = _itemGroup.get(0);
            int position = item.getPosition();
            String format = item.getItem().getFormat().toLowerCase();
            String responseType = item.getItem().getResponseType().toLowerCase().replace(" ", "");

            // create styles
            StringBuilder styles = new StringBuilder();
            styles.append("multipleChoiceItem itemContainer ");
            styles.append(TDSStringUtils.format("format_{0} ", format));
            styles.append(TDSStringUtils.format("response_{0}", responseType));

            final String div_item = "<div id=\"Item_{0}\" class=\"{1}\">";
            addHtml(TDSStringUtils.format(div_item, position, styles));
        }

        // We have three placeholders: preamble, content, and postamble.
        // we have already added layout to the content placeholder and hence the
        // following statement is not required.
        /*
         * // add rendered layout and templates to the page this.Controls.Add(layout
         * as UserControl);
         */
        // </div> (itemContainer)
        // ANYTHING AFTER THIS COMMENT NEEDS TO BE ADDED TO _postamblePlaceHolder

        if (settings.getIncludeItemWrapepr() && layout instanceof LayoutSingle && _itemGroup.getHasItems())
            addHtml("</div>", _postamblePlaceHolder);

        if (settings.getIncludeItemWrapepr()) {
            // </div> (pageLayout)
            addHtml("</div>", _postamblePlaceHolder);
            // javascript objects
            if (settings.getIncludeJson()) {
                ITSDocumentJsonSerializable itsJson = new ITSDocumentJsonSerializable(_itemGroup, _layoutName,
                        _layoutLanguage);
                addScript(itsJson.createJson());
            }
        }
    }

    // / <summary>
    // / Get illustration template filled out with the illustration content.
    // / </summary>
    protected ITemplateStimulus getTemplateIllustration(UIComponent parent) {
        return JsfHelpers.<ITemplateStimulus, ITemplateStimulus>includeCompositeComponent(parent,
                getTemplateFolder(), this._templateFileIllustration, null, null, null);
    }

    // / <summary>
    // / Get illustration template filled out with the illustration content.
    // / </summary>
    protected UIComponent getTemplateIllustration(IItemRender itemRender, UIComponent parent) {
        ITSContent itsContent = getContent(itemRender.getItem());

        // check if there is illustration content
        if (itsContent == null || StringUtils.isEmpty(itsContent.getIllustration()))
            return null;

        // load illustration template
        ITemplateStimulus templateIllustration = getTemplateIllustration(parent);
        if (templateIllustration == null)
            return null;

        // set illustration html
        templateIllustration.getContent().setText(itsContent.getIllustration());
        return templateIllustration;
    }

    // / <summary>
    // / Checks the item set for any possible rendering problems
    // / </summary>
    // / <returns>true means there is an error, false means items should be
    // ok</returns>
    private boolean checkContentErrors() {
        ItemRenderGroup itemGroup = getItemRenderGroup();
        if (itemGroup == null) {
            addError(ErrorCategories.Data, "Error loading content: No item group assigned");
            return true;
        }

        for (IItemRender render : itemGroup) {
            if (!render.getItem().getIsLoaded()) {
                String error = "There is a problem with one of the questions in this set.";

                try {
                    addError(ErrorCategories.Content,
                            "Error loading content: " + error + "', ITEM = " + render.getItem().getBaseUri());
                } catch (Exception exp) {
                    addError(ErrorCategories.Content, "Error loading content: ITEM = "
                            + render.getItem().getBaseUri() + " ; Exception message: " + exp.getMessage());
                }
                return true;
            }
        }
        return false;
    }

    private void addError(ErrorCategories error, String message, Object... args) {
        setErrorCategory(error);
        setErrorDescription(TDSStringUtils.format(message, args));
    }

    private ILayout loadLayout(String layoutFile, UIComponent parent) {
        ILayout layout = JsfHelpers.<ILayout, ILayout>includeCompositeComponent(parent, getLayoutFolder(),
                layoutFile, null, null, null);
        return layout;
    }

    // / <summary>
    // / Sets data used for rendering on the layout or template.
    // / </summary>
    private void setRenderData(Object control) {
        RendererBase rendererBase = (RendererBase) control;
        if (rendererBase != null) {
            rendererBase.setGroup(getItemRenderGroup());
        }
    }

    // / <summary>
    // / Sets data used for rendering on the layout or template.
    // / </summary>
    // / <param name="control">Layout or Template object</param>
    // / <param name="itemRender">Item renderer object</param>
    private void setRenderData(Object control, IItemRender itemRender) {
        RendererBase rendererBase = (RendererBase) control;

        if (rendererBase != null) {
            rendererBase.setGroup(getItemRenderGroup());
            rendererBase.setData(itemRender);
            rendererBase.setDocument(itemRender.getItem());
        }
    }

    // / <summary>
    // / Get a documents response type. This will also check if document is
    // overrided.
    // / </summary>
    private String getResponseType(IITSDocument doc) {
        // Do we use the seperate response type or the one that is part of the
        // layout name?
        String responseType = StringUtils.isEmpty(getResponseTypeOverride()) ? doc.getResponseType()
                : getResponseTypeOverride();

        // HACK: If the response type is vertical and there is a sound element in
        // the options then
        // we need to manually set the response type to include sound (e.x.,
        // "Vertical Sound").
        if (!StringUtils.isEmpty(responseType) && StringUtils.equalsIgnoreCase(responseType, "vertical")) {
            ITSContent content = getContent(doc);

            if (content != null && content.getOptions() != null) {
                for (ITSOption option : content.getOptions()) {
                    if (option.getSound() != null) {
                        responseType += " Sound";
                        break;
                    }
                }
            }
        }

        // HACK: If the layout is used for accessibility (TemplateWAI=true) and this
        // is item is MC then
        // use our custom MC template.
        if (_templateFileWAI && isMC(doc.getFormat(), responseType))
            return "Vertical WAI";

        return responseType;
    }

    // / <summary>
    // / Check if a document is considered a multiple choice question. This will
    // check format and response type.
    // / </summary>
    private boolean isMC(String format, String responseType) {
        // check format
        if (format != null) {
            if (StringUtils.equals(format.toUpperCase(), "MC"))
                return true;
        }

        // check response type
        if (!StringUtils.isEmpty(responseType)) {
            switch (responseType.toUpperCase()) {
            case "VERTICAL":
            case "VERTICAL SOUND":
            case "HORIZONTAL":
            case "STACKED":
            case "STACKEDB":
                return true;
            }
        }
        return false;
    }

    // / <summary>
    // / Check if we can bind options to this response type.
    // / </summary>
    private boolean hasBindableOptions(String format, String responseType) {
        // check if valid format data
        if (format == null)
            return false;
        format = format.toUpperCase();

        // check if scaffolding item
        if (StringUtils.equals(format, "ASI"))
            return true;

        // check if multiple choice/select
        return (isMC(format, responseType) || isMS(format, responseType));
    }

    // / <summary>
    // / Check if a document is considered a multiple select question. This will
    // check format and response type.
    // / </summary>
    private boolean isMS(String format, String responseType) {
        // check format
        if (format != null) {
            if (StringUtils.equals(format.toUpperCase(), "MS"))
                return true;
        }
        // check response type
        if (!StringUtils.isEmpty(responseType)) {
            switch (responseType.toUpperCase()) {
            case "VERTICAL MS":
                return true;
            }
        }
        return false;
    }

    private IResponseLayout getResponseControl(String name, UIComponent parent) {
        return JsfHelpers.<IResponseLayout, IResponseLayout>includeCompositeComponent(parent,
                getResponseTypesFolder(), "Response_" + name + ".xhtml", null, null, null);
    }
}

class UserComponent {
    public String _libraryName;
    public String _resourceName;
}