DynamicViewCustomizer

package mcl.reports;

/*
 * The latest version is available from https://github.com/jesse-gallagher/Domino-One-Offs/blob/master/mcl/reports/DynamicViewCustomizer.java
 */


import java.io.*;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import lotus.domino.*;

import com.ibm.commons.util.SystemCache;
import com.ibm.xsp.FacesExceptionEx;
import com.ibm.xsp.extlib.builder.ControlBuilder.IControl;
import com.ibm.xsp.extlib.component.dynamicview.UIDynamicViewPanel;
import com.ibm.xsp.extlib.component.dynamicview.ViewColumnConverter;
import com.ibm.xsp.extlib.component.dynamicview.ViewDesign;
import com.ibm.xsp.extlib.component.dynamicview.DominoDynamicColumnBuilder.DominoViewCustomizer;
import com.ibm.xsp.extlib.component.dynamicview.ViewDesign.ColumnDef;
import com.ibm.xsp.extlib.component.dynamicview.ViewDesign.DefaultColumnDef;
import com.ibm.xsp.extlib.component.dynamicview.ViewDesign.DefaultViewDef;
import com.ibm.xsp.extlib.component.dynamicview.ViewDesign.ViewDef;
import com.ibm.xsp.extlib.component.dynamicview.ViewDesign.ViewFactory;
import com.ibm.xsp.extlib.util.ExtLibUtil;
import com.ibm.xsp.model.domino.wrapped.DominoViewEntry;
import com.raidomatic.xml.XMLDocument;
import com.raidomatic.xml.XMLNode;


public class DynamicViewCustomizer extends DominoViewCustomizer implements Serializable {
	private static final long serialVersionUID = -5126984721484501732L;

	private String panelId = "";

	@Override
	public ViewFactory getViewFactory() {
		return new DynamicViewFactory();
	}

	public static class DynamicViewFactory implements ViewFactory, Serializable {
		private static final long serialVersionUID = 123034173761337005L;
		private SystemCache views = new SystemCache("View Definition", 16, "xsp.extlib.viewdefsize");

		private void writeObject(ObjectOutputStream out) throws IOException {
			this.views = null;
			out.defaultWriteObject();
		}
		private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
			in.defaultReadObject();
			this.views = new SystemCache("View Definition", 16, "xsp.extlib.viewdefsize");
		}

		public ViewDef getViewDef(View view) {
			if(view == null) {
				return null;
			}
			try {
				String viewKey = ViewDesign.getViewKey(view);
				DefaultViewDef viewDef = (DefaultViewDef)this.views.get(viewKey);
				if(viewDef == null) {
					// Read the view
					viewDef = new DefaultViewDef();
					if(view.isHierarchical()) { viewDef.flags |= DefaultViewDef.FLAG_HIERARCHICAL; }
					if(view.isCategorized()) { viewDef.flags |= DefaultViewDef.FLAG_CATEGORIZED; }

					viewDef.columns.addAll(this.getViewColumnInformation(view));
				}
				return viewDef;
			} catch(Exception ex) {
				throw new FacesExceptionEx(ex, "Error while accessing view {0}", view.toString());
			}
		}

		@SuppressWarnings("unchecked")
		private List<ColumnDef> getViewColumnInformation(View view) throws Exception {
			Database database = view.getParent();

			/* Generate the DXL */
			Document viewDoc = database.getDocumentByUNID(view.getUniversalID());
			String dxl = viewDoc.generateXML();
			InputStream is = new ByteArrayInputStream(dxl.getBytes(Charset.forName("UTF-8")));
			XMLDocument dxlDoc = new XMLDocument();
			dxlDoc.loadInputStream(is);
			viewDoc.recycle();

			/*
			 * Fetch both types of column information since some properties are
			 * much easier to deal with via the standard API
			 */
			List<ViewColumn> viewColumns = view.getColumns();
			List<XMLNode> dxlColumns = dxlDoc.selectNodes("//column");

			// Figure out if we're going to extend the last column
			boolean extendLastColumn = dxlDoc.selectSingleNode("//view").getAttribute("extendlastcolumn").equals("true");

			Document contextDoc = database.createDocument();
			List<ColumnDef> columns = new Vector<ColumnDef>();
			String activeColorColumn = "";
			for(int i = 0; i < dxlColumns.size(); i++) {
				XMLNode columnNode = dxlColumns.get(i);
				ViewColumn viewColumn = viewColumns.get(i);

				ExtendedColumnDef column = new ExtendedColumnDef();
				column.replicaId = view.getParent().getReplicaID();

				if(columnNode.getAttribute("hidden").equals("true")) {
					column.flags |= DefaultColumnDef.FLAG_HIDDEN;
				} else {
					// Check to see if it's hidden by a hide-when formula
					XMLNode hideWhen = columnNode.selectSingleNode("code[@event='hidewhen']");
					if(hideWhen != null) {
						if(hideWhen.getAttribute("enabled") == null || !hideWhen.getAttribute("enabled").equals("false")) {
							String hideWhenFormula = hideWhen.getText();
							if(hideWhenFormula.length() > 0) {
								List<Object> evalResult = ExtLibUtil.getCurrentSession().evaluate(hideWhenFormula, contextDoc);
								if(evalResult.size() > 0 && evalResult.get(0) instanceof Double && (Double)evalResult.get(0) == 1) {
									column.flags |= DefaultColumnDef.FLAG_HIDDEN;
								}
							}
						}
					}
				}

				column.name = columnNode.getAttribute("itemname");

				if(columnNode.getAttribute("showascolor").equals("true")) {
					activeColorColumn = column.name;
				}
				column.colorColumn = activeColorColumn;

				// Get the header information
				XMLNode header = columnNode.selectSingleNode("columnheader");
				column.title = columnNode.selectSingleNode("columnheader").getAttribute("title");
				if(header.getAttribute("align").equals("center")) {
					column.flags |= DefaultColumnDef.FLAG_HALIGNCENTER;
				} else if(header.getAttribute("align").equals("right")) {
					column.flags |= DefaultColumnDef.FLAG_HALIGNRIGHT;
				}

				column.width = new Float(columnNode.getAttribute("width")).intValue();
				column.actualWidth = Double.parseDouble(columnNode.getAttribute("width"));

				if(columnNode.getAttribute("responsesonly").equals(true)) {
					column.flags |= DefaultColumnDef.FLAG_RESPONSE;
				}
				if(columnNode.getAttribute("categorized").equals("true")) {
					column.flags |= DefaultColumnDef.FLAG_CATEGORIZED;
				}
				if(columnNode.getAttribute("sort").length() > 0) {
					column.flags |= DefaultColumnDef.FLAG_SORTED;
				}
				if(columnNode.getAttribute("resort").equals("ascending") || columnNode.getAttribute("resort").equals("both")) {
					column.flags |= DefaultColumnDef.FLAG_RESORTASC;
				}
				if(columnNode.getAttribute("resort").equals("descending") || columnNode.getAttribute("resort").equals("both")) {
					column.flags |= DefaultColumnDef.FLAG_RESORTDESC;
				}
				if(columnNode.getAttribute("align").equals("center")) {
					column.flags |= DefaultColumnDef.FLAG_ALIGNCENTER;
				} else if(columnNode.getAttribute("align").equals("right")) {
					column.flags |= DefaultColumnDef.FLAG_ALIGNRIGHT;
				}
				if(columnNode.getAttribute("showaslinks").equals("true")) {
					column.flags |= DefaultColumnDef.FLAG_LINK | DefaultColumnDef.FLAG_ONCLICK | DefaultColumnDef.FLAG_CHECKBOX;
				}

				column.numberFmt = viewColumn.getNumberFormat();
				column.numberDigits = viewColumn.getNumberDigits();
				column.numberAttrib = viewColumn.getNumberAttrib();
				if(viewColumn.isNumberAttribParens()) {
					column.flags |= DefaultColumnDef.FLAG_ATTRIBPARENS;
				}
				if(viewColumn.isNumberAttribPercent()) {
					column.flags |= DefaultColumnDef.FLAG_ATTRIBPERCENT;
				}
				if(viewColumn.isNumberAttribPunctuated()) {
					column.flags |= DefaultColumnDef.FLAG_ATTRIBPUNC;
				}
				column.timeDateFmt = viewColumn.getTimeDateFmt();
				column.dateFmt = viewColumn.getDateFmt();
				column.timeFmt = viewColumn.getTimeFmt();
				column.timeZoneFmt = viewColumn.getTimeZoneFmt();
				column.listSep = viewColumn.getListSep();

				column.fontFace = viewColumn.getFontFace();
				column.fontStyle = viewColumn.getFontStyle();
				column.fontPointSize = viewColumn.getFontPointSize();
				column.fontColor = viewColumn.getFontColor();

				column.headerFontFace = viewColumn.getHeaderFontFace();
				column.headerFontStyle = viewColumn.getHeaderFontStyle();
				column.headerFontPointSize = viewColumn.getHeaderFontPointSize();
				column.headerFontColor = viewColumn.getHeaderFontColor();

				if(columnNode.getAttribute("showasicons").equals("true")) {
					column.flags |= DefaultColumnDef.FLAG_ICON;
				}
				if(columnNode.getAttribute("twisties").equals("true")) {
					column.flags |= DefaultColumnDef.FLAG_INDENTRESP;
				}

				// Find any twistie image
				XMLNode twistieImage = columnNode.selectSingleNode("twistieimage/imageref");
				if(twistieImage != null) {
					if(twistieImage.getAttribute("database").equals("0000000000000000")) {
						column.twistieReplicaId = database.getReplicaID();
					} else {
						column.twistieReplicaId = twistieImage.getAttribute("database");
					}

					// Make sure that the referenced database is available on
					// the current server
					boolean setTwistie = true;
					if(!column.twistieReplicaId.equalsIgnoreCase(database.getReplicaID())) {
						Database twistieDB = ExtLibUtil.getCurrentSession().getDatabase("", "");
						twistieDB.openByReplicaID("", column.twistieReplicaId);
						if(!twistieDB.isOpen()) {
							setTwistie = false;
						}
						twistieDB.recycle();
					}
					if(setTwistie) {
						column.twistieImage = twistieImage.getAttribute("name");
					}

				}

				// Support extending the column width to the full window.
				// In the client, "extend last column" takes priority
				if(extendLastColumn && i == dxlColumns.size() - 1) {
					column.extendColumn = true;
				} else if(!extendLastColumn && columnNode.getAttribute("extwindowwidth").equals("true")) {
					column.extendColumn = true;
				}

				columns.add(column);

				viewColumn.recycle();
			}
			contextDoc.recycle();

			database.recycle();

			return columns;
		}

		public static class ExtendedColumnDef extends DefaultColumnDef implements Serializable {
			private static final long serialVersionUID = 5158008403553374867L;

			public String colorColumn;
			public String twistieImage = "";
			public String twistieReplicaId = "";
			public boolean extendColumn = false;
			public String replicaId = "";
			public double actualWidth;

			public String fontFace = "";
			public int fontPointSize = 0;
			public int fontStyle = 0;
			public int fontColor = 0;

			public String headerFontFace = "";
			public int headerFontPointSize = 0;
			public int headerFontStyle = 0;
			public int headerFontColor = 0;
		}
	}
	@Override
	public IControl createColumn(FacesContext context, UIDynamicViewPanel panel, int index, ColumnDef colDef) {
		this.panelId = panel.getId();
		return super.createColumn(context, panel, index, colDef);
	}

	@Override
	public void afterCreateColumn(FacesContext context, int index, ColumnDef colDef, IControl column) {
		UIDynamicViewPanel panel = (UIDynamicViewPanel)ExtLibUtil.getComponentFor(context.getViewRoot(), panelId);

		// Patch in a converter to handle special text and icon columns
		UIDynamicViewPanel.DynamicColumn col = (UIDynamicViewPanel.DynamicColumn)column.getComponent();
		if(colDef.isIcon()) {
			// For icons, override the default behavior so it can handle
			// string-based ones
			col.setValueBinding("iconSrc", null);
			col.setDisplayAs("");
			col.setConverter(new IconColumnConverter(null, colDef, panel));
		} else {
			col.setConverter(new ExtendedViewColumnConverter(null, colDef, panel));
		}

		// Apply a general style class to indicate that it's not just some
		// normal view panel column
		// Many style attributes will be class-based both for flexibility and
		// because headers can't have style applied directly
		String styleClass = " notesViewColumn";
		String headerStyleClass = "";

		// Add an extra class for category columns
		if(colDef.isCategorized()) {
			styleClass += " notesViewCategory";
		}

		// We'll handle escaping the HTML manually, to support
		// [<b>Notes-style</b>] pass-through-HTML and icon columns
		col.setContentType("html");

		// Deal with any twistie images and color columns
		if(colDef instanceof DynamicViewFactory.ExtendedColumnDef) {
			DynamicViewFactory.ExtendedColumnDef extColDef = (DynamicViewFactory.ExtendedColumnDef)colDef;

			if(extColDef.twistieImage.length() > 0) {
				// Assume that it's a multi-image well for now
				col.setCollapsedImage("/.ibmxspres/domino/__" + extColDef.twistieReplicaId + ".nsf/" + extColDef.twistieImage.replaceAll("\\\\", "/") + "?Open&ImgIndex=2");
				col.setExpandedImage("/.ibmxspres/domino/__" + extColDef.twistieReplicaId + ".nsf/" + extColDef.twistieImage.replaceAll("\\\\", "/") + "?Open&ImgIndex=1");
			}

			// The style applies to the contents of the column as well as the
			// column itself, which messes with icon columns
			// For now, don't apply it at all to those columns
			String style = "";
			if(!extColDef.extendColumn) {
				style = "max-width: " + (extColDef.actualWidth * extColDef.fontPointSize * 1.3) + "px; min-width: " + (extColDef.actualWidth * extColDef.fontPointSize * 1.3) + "px";
			} else {
				style = "width: 100%";
			}

			// Check for left or right alignment
			switch(extColDef.getAlignment()) {
			case ViewColumn.ALIGN_CENTER:
				styleClass += " notesViewAlignCenter";
				break;
			case ViewColumn.ALIGN_RIGHT:
				styleClass += " notesViewAlignRight";
				break;
			}

			// Add font information
			styleClass += this.fontStyleToStyleClass(extColDef.fontStyle);
			headerStyleClass += this.fontStyleToStyleClass(extColDef.headerFontStyle);
			style += "; color: " + this.notesColorToCSS(extColDef.fontColor);

			if(extColDef.colorColumn.length() > 0) {
				String styleFormula = "#{javascript:'" + style.replace("'", "\\'") + ";' + " + this.getClass().getName() + ".colorColumnToStyle(" + panel.getVar() + ".getColumnValue('"
				+ extColDef.colorColumn + "'))}";
				ValueBinding styleBinding = context.getApplication().createValueBinding(styleFormula);
				col.setValueBinding("style", styleBinding);
			} else {
				col.setStyle(style);
			}

		}
		col.setStyleClass((col.getStyleClass() == null ? "" : col.getStyleClass()) + styleClass);
		col.setHeaderClass((col.getHeaderClass() == null ? "" : col.getHeaderClass()) + headerStyleClass);
	}

	public static class ExtendedViewColumnConverter extends ViewColumnConverter {
		private ColumnDef colDef;
		private String panelId;

		// For loading the state
		public ExtendedViewColumnConverter() {}

		public ExtendedViewColumnConverter(ViewDef viewDef, ColumnDef colDef, UIDynamicViewPanel panel) {
			super(viewDef, colDef);
			this.colDef = colDef;
			this.panelId = panel.getId();
		}

		@Override
		public String getAsString(FacesContext context, UIComponent component, Object value) {
			UIDynamicViewPanel panel = (UIDynamicViewPanel)ExtLibUtil.getComponentFor(context.getViewRoot(), panelId);

			// First, apply any column-color info needed
			DominoViewEntry entry = this.resolveViewEntry(context, panel.getVar());
			try {
				if(value instanceof DateTime) {
					return this.getValueDateTimeAsString(context, component, ((DateTime)value).toJavaDate());
				}
				if(value instanceof Date) {
					return this.getValueDateTimeAsString(context, component, (Date)value);
				}
				if(value instanceof Number) {
					return this.getValueNumberAsString(context, component, (Number)value);
				}
			} catch(NotesException ex) {}

			String stringValue = value.toString();

			try {
				stringValue = specialTextDecode(stringValue, entry);
			} catch(NotesException ne) {}

			// Process the entry as Notes-style pass-through-HTML
			if(!entry.isCategory()) {
				stringValue = this.handlePassThroughHTML(stringValue);
			}

			// Add in some text for empty categories
			if(entry.isCategory() && stringValue.length() == 0) {
				stringValue = "(Not Categorized)";
			}

			// Include a &nbsp; to avoid weird styling problems when the content
			// itself is empty or not visible
			return stringValue;
		}

		private String handlePassThroughHTML(String cellData) {
			if(cellData.contains("[<") && cellData.contains(">]")) {
				String[] cellChunks = cellData.split("\\[\\<", -2);
				cellData = "";
				for(String chunk : cellChunks) {
					if(chunk.contains(">]")) {
						String[] smallChunks = chunk.split(">]", -2);
						cellData += "<" + smallChunks[0] + ">" + xmlEncode(smallChunks[1]);
					} else {
						cellData += xmlEncode(chunk);
					}
				}
			} else {
				cellData = xmlEncode(cellData);
			}
			return cellData;
		}

		private DominoViewEntry resolveViewEntry(FacesContext context, String var) {
			return (DominoViewEntry)context.getApplication().getVariableResolver().resolveVariable(context, var);
		}

		@Override
		public Object saveState(FacesContext context) {
			Object[] superState = (Object[])super.saveState(context);
			Object[] state = new Object[3];
			state[0] = superState;
			state[1] = this.colDef;
			state[2] = this.panelId;
			return state;
		}

		@Override
		public void restoreState(FacesContext context, Object value) {
			Object[] state = (Object[])value;
			super.restoreState(context, state[0]);
			this.colDef = (ColumnDef)state[1];
			this.panelId = (String)state[2];
		}
	}

	public static class IconColumnConverter extends ViewColumnConverter {
		private ColumnDef colDef;

		// For loading the state
		public IconColumnConverter() {}

		public IconColumnConverter(ViewDef viewDef, ColumnDef colDef, UIDynamicViewPanel panel) {
			super(viewDef, colDef);
			this.colDef = colDef;
		}

		@SuppressWarnings("unchecked")
		@Override
		public String getAsString(FacesContext context, UIComponent component, Object value) {
			List<Object> listValue;
			if(value instanceof List) {
				listValue = (List<Object>)value;
			} else {
				listValue = new Vector<Object>();
				listValue.add(value);
			}
			StringBuilder result = new StringBuilder();

			result.append("<span style='white-space: nowrap'>");
			for(Object node : listValue) {
				// Handle a zero-value icon specially
				if(node instanceof Double && ((Double)node == 0 || (Double)node == 999)) {
					result.append("<img class='notesViewIconCustom notesViewIconBlank' src='/icons/ecblank.gif' />");
				} else if(node instanceof Double) {
					result.append("<img class='notesViewIconStandard' src='/icons/vwicn");
					Double num = (Double)node;
					if(num < 10) {
						result.append("00");
					} else if(num < 100) {
						result.append("0");
					}
					result.append(num.intValue());
					result.append(".gif' />");
				} else {
					if(String.valueOf(value).length() > 0 && !String.valueOf(value).equals("null")) {
						DynamicViewFactory.ExtendedColumnDef col = (DynamicViewFactory.ExtendedColumnDef)this.colDef;
						try {
							result.append("<img class='notesViewIconCustom' src='");
							result.append("/__" + col.replicaId + ".nsf/" + java.net.URLEncoder.encode(String.valueOf(node), "UTF-8"));
							result.append("' />");
						} catch(Exception e) {}
					}
				}
			}
			result.append("</span>");

			return result.toString();
		}

		@Override
		public Object saveState(FacesContext context) {
			Object[] superState = (Object[])super.saveState(context);
			Object[] state = new Object[2];
			state[0] = superState;
			state[1] = this.colDef;
			return state;
		}

		@Override
		public void restoreState(FacesContext context, Object value) {
			Object[] state = (Object[])value;
			super.restoreState(context, state[0]);
			this.colDef = (ColumnDef)state[1];
		}

	}

	@SuppressWarnings("unchecked")
	public static String colorColumnToStyle(Object colorValuesObj) {
		String cellStyle = "";
		if(colorValuesObj instanceof List) {
			List<Double> colorValues = (List<Double>)colorValuesObj;
			if(colorValues.size() > 3) {
				// Then we have a background color
				if(colorValues.get(0) != -1) {
					// Then the background is not pass-through
					cellStyle = "background-color: rgb(" + colorValues.get(0).intValue() + ", " + colorValues.get(1).intValue() + ", " + colorValues.get(2).intValue() + ");";
				} else {
					cellStyle = "";
				}
				if(colorValues.get(3) != -1) {
					// Then main color is not pass-through
					cellStyle += "color: rgb(" + colorValues.get(3).intValue() + ", " + colorValues.get(4).intValue() + ", " + colorValues.get(5).intValue() + ");";
				}
			} else {
				// Then it's just a text color
				if(colorValues.get(0) != -1) {
					cellStyle += "color: rgb(" + colorValues.get(0).intValue() + ", " + colorValues.get(1).intValue() + ", " + colorValues.get(2).intValue() + ");";
				}
			}
		}
		return cellStyle;
	}

	private String fontStyleToStyleClass(int fontStyle) {
		StringBuilder result = new StringBuilder();

		if((fontStyle & ViewColumn.FONT_PLAIN) != 0) {
			result.append(" notesViewPlain");
		}
		if((fontStyle & ViewColumn.FONT_BOLD) != 0) {
			result.append(" notesViewBold");
		}
		if((fontStyle & ViewColumn.FONT_UNDERLINE) != 0) {
			result.append(" notesViewUnderline");
		}
		if((fontStyle & ViewColumn.FONT_STRIKETHROUGH) != 0) {
			result.append(" notesViewStrikethrough");
		}
		if((fontStyle & ViewColumn.FONT_ITALIC) != 0) {
			result.append(" notesViewItalic");
		}

		return result.toString();
	}

	private String notesColorToCSS(int notesColor) {
		try {
			Session session = ExtLibUtil.getCurrentSession();
			ColorObject colorObject = session.createColorObject();
			colorObject.setNotesColor(notesColor);

			StringBuilder result = new StringBuilder();
			result.append("rgb(");
			result.append(colorObject.getRed());
			result.append(",");
			result.append(colorObject.getGreen());
			result.append(",");
			result.append(colorObject.getBlue());
			result.append(")");

			return result.toString();
		} catch(NotesException ne) {
			return "";
		}
	}

	public static String strLeft(String input, String delimiter) {
		return input.substring(0, input.indexOf(delimiter));
	}
	public static String strRight(String input, String delimiter) {
		return input.substring(input.indexOf(delimiter) + delimiter.length());
	}
	public static String strLeftBack(String input, String delimiter) {
		return input.substring(0, input.lastIndexOf(delimiter));
	}
	public static String strLeftBack(String input, int chars) {
		return input.substring(0, input.length() - chars);
	}
	public static String strRightBack(String input, String delimiter) {
		return input.substring(input.lastIndexOf(delimiter) + delimiter.length());
	}
	public static String strRightBack(String input, int chars) {
		return input.substring(input.length() - chars);
	}

	public static String xmlEncode(String text) {
		StringBuilder result = new StringBuilder();

		for(int i = 0; i < text.length(); i++) {
			char currentChar = text.charAt(i);
			if(!((currentChar >= 'a' && currentChar <= 'z') || (currentChar >= 'A' && currentChar <= 'Z') || (currentChar >= '0' && currentChar <= '9'))) {
				result.append("&#" + (int)currentChar + ";");
			} else {
				result.append(currentChar);
			}
		}

		return result.toString();
	}

	public static String specialTextDecode(String specialText, ViewEntry viewEntry) throws NotesException {
		String result = specialText;

		String specialStart = "";
		String specialEnd = "�";

		// First, find the start and end of the special text
		int start_pos = result.indexOf(specialStart);
		int end_pos = result.indexOf(specialEnd);

		int loopStopper = 1;
		while(start_pos > -1 && end_pos > start_pos && loopStopper < 100) {
			loopStopper++;

			String working = result.substring(start_pos+1, end_pos);
			String midResult = "";
			String[] choices;
			int offset, length, parameterCount;

			switch(working.charAt(0)) {
			case 'C':
				// @DocChildren
				parameterCount = Integer.parseInt(working.substring(1, 2));
				switch(parameterCount) {
				case 0:
					midResult = viewEntry.getChildCount() + "";
					break;
				case 1:
					midResult = strRight(working, "=").replaceAll("%", viewEntry.getChildCount() + "");
					break;
				case 2:
					// For convenience, I'll break the string into each option, even if I only use one
					choices = new String[] { "", "" };

					// I can cheat a bit on the first one to find the length
					offset = 0;
					length = Integer.parseInt(strLeft(strRight(working, ";"), "="));
					choices[0] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 1 + length;
					choices[1] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					if(viewEntry.getChildCount() == 0) {
						midResult = choices[0].replaceAll("%", "0");
					} else {
						midResult = choices[1].replaceAll("%", viewEntry.getChildCount() + "");
					}

					break;
				case 3:
					// For convenience, I'll break the string into each option, even if I only use one
					choices = new String[] { "", "", "" };

					// I can cheat a bit on the first one to find the length
					offset = 0;
					length = Integer.parseInt(strLeft(strRight(working, ";"), "="));
					choices[0] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 2 + length;
					length = Integer.parseInt(working.substring(offset, working.indexOf("=", offset)));
					choices[1] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 2 + length;
					length = Integer.parseInt(working.substring(offset, working.indexOf("=", offset)));
					choices[2] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					if(viewEntry.getChildCount() == 0) {
						midResult = choices[0].replaceAll("%", "0");
					} else if(viewEntry.getChildCount() == 1) {
						midResult = choices[1].replaceAll("%", "1");
					} else {
						midResult = choices[2].replaceAll("%", viewEntry.getChildCount() + "");
					}

					break;
				}
				break;
			case 'D':
				// @DocDescendants
				parameterCount = Integer.parseInt(working.substring(1, 2));
				switch(parameterCount) {
				case 0:
					midResult = viewEntry.getDescendantCount() + "";
					break;
				case 1:
					midResult = strRight(working, "=").replaceAll("%", viewEntry.getDescendantCount() + "");
					break;
				case 2:
					// For convenience, I'll break the string into each option, even if I only use one
					choices = new String[] { "", "" };

					// I can cheat a bit on the first one to find the length
					offset = 0;
					length = Integer.parseInt(strLeft(strRight(working, ";"), "="));
					choices[0] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 1 + length;
					choices[1] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					if(viewEntry.getDescendantCount() == 0) {
						midResult = choices[0].replaceAll("%", "0");
					} else {
						midResult = choices[1].replaceAll("%", viewEntry.getDescendantCount() + "");
					}

					break;
				case 3:
					// For convenience, I'll break the string into each option, even if I only use one
					choices = new String[] { "", "", "" };

					// I can cheat a bit on the first one to find the length
					offset = 0;
					length = Integer.parseInt(strLeft(strRight(working, ";"), "="));
					choices[0] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 2 + length;
					length = Integer.parseInt(working.substring(offset, working.indexOf("=", offset)));
					choices[1] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 2 + length;
					length = Integer.parseInt(working.substring(offset, working.indexOf("=", offset)));
					choices[2] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					if(viewEntry.getDescendantCount() == 0) {
						midResult = choices[0].replaceAll("%", "0");
					} else if(viewEntry.getDescendantCount() == 1) {
						midResult = choices[1].replaceAll("%", "1");
					} else {
						midResult = choices[2].replaceAll("%", viewEntry.getDescendantCount() + "");
					}

					break;
				}
				break;
			case 'H':
				// @DocLevel
				midResult = (viewEntry.getIndentLevel()+1) + ""; 
				break;
			case 'A':
				// @DocNumber
				parameterCount = Integer.parseInt(working.substring(1, 2));
				switch(parameterCount) {
				case 0:
					midResult = viewEntry.getPosition('.');
					break;
				case 1:
					String delimiter = strRight(working, "=");
					if(delimiter.length() == 0) {
						midResult = strRightBack(viewEntry.getPosition('.'), ".");
					} else if(delimiter.length() > 1) {
						// Mimic formula's weird behavior for multi-character strings
						midResult = delimiter;
					} else {
						midResult = viewEntry.getPosition(delimiter.charAt(0));
					}
					break;
				}
				break;
			case 'J':
				// @DocParentNumber
				if(viewEntry.getIndentLevel() == 0) {
					midResult = "";
				} else {
					parameterCount = Integer.parseInt(working.substring(1, 2));
					switch(parameterCount) {
					case 0:
						midResult = strLeftBack(viewEntry.getPosition('.'), ".");
						break;
					case 1:
						String delimiter = strRight(working, "=");
						if(delimiter.length() == 0) {
							midResult = strRightBack(strLeftBack(viewEntry.getPosition('.'), "."), ".");
						} else if(delimiter.length() > 1) {
							// Mimic formula's weird behavior for multi-character strings
							midResult = delimiter;
						} else {
							midResult = strLeftBack(viewEntry.getPosition(delimiter.charAt(0)), delimiter);
						}
						break;
					}
				}
				break;
			case 'B':
				// @DocSiblings
				midResult = (viewEntry.getSiblingCount()) + "";
				break;
			case 'I':
				// @IsCategory
				parameterCount = Integer.parseInt(working.substring(1, 2));
				switch(parameterCount) {
				case 0:
					midResult = viewEntry.isCategory() ? "*" : "";
					break;
				case 1:
					midResult = viewEntry.isCategory() ? strRight(working, "=") : "";
					break;
				case 2:
					// For convenience, I'll break the string into each option, even if I only use one
					choices = new String[] { "", "" };
					offset = 0;
					length = Integer.parseInt(strLeft(strRight(working, ";"), "="));
					choices[0] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					offset = working.indexOf("=", offset) + 2 + length;
					length = Integer.parseInt(working.substring(offset, working.indexOf("=", offset)));
					choices[1] = working.substring(working.indexOf("=", offset)+1, working.indexOf("=", offset)+1+length);

					midResult = viewEntry.isCategory() ? choices[0] : choices[1];

					break;
				}

				break;
			case 'G':
				// @IsExpandable
				midResult = "";
				break;
			default:
				midResult = working;
			break;
			}

			result = result.replaceAll(specialStart + working + specialEnd, midResult);

			start_pos = result.indexOf(specialStart);
			end_pos = result.indexOf(specialEnd);
		}

		return result;
	}
}





This is a dynamic view customizer class intended for use with the customizerBean property of the ExtLib's Dynamic View control. Its goal is to more closely replicate the original design of the view, including color columns, hide-when formulas, twistie images, and the like.

The code refers to my XML convenience wrapper classes, available at https://github.com/jesse-gallagher/Domino-One-Offs/tree/master/com/raidomatic/xml under the same Apache license.

Java
Jesse Gallagher
September 30, 2012 11:02 AM
Rating
301

All code submitted to OpenNTF XSnippets, whether submitted as a "Snippet" or in the body of a Comment, is provided under the Apache License Version 2.0. See Terms of Use for full details.



No comments yetLogin first to comment...