001: package jimm.datavision;
002:
003: import jimm.datavision.field.Field;
004: import jimm.datavision.source.DataSource;
005: import jimm.util.XMLWriter;
006:
007: /**
008: * A group uses a {@link Selectable} object to define record grouping. A group
009: * may contain multiple header and footer sections.
010: *
011: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
012: */
013: public class Group implements Writeable {
014:
015: /**
016: * Sort records by the group's selectable in ascending order (the default).
017: */
018: public static final int SORT_ASCENDING = 0;
019: /**
020: * Sort records by the group's selectable in descending order.
021: */
022: public static final int SORT_DESCENDING = 1;
023:
024: protected Report report;
025: protected SectionArea headers;
026: protected SectionArea footers;
027: protected Selectable selectable;
028: protected int sortOrder;
029: protected Object value; // Current row value
030: protected boolean newValue;
031: protected boolean firstValue;
032: protected int recordCount;
033:
034: /**
035: * Creates a new group and gives it a header section containing a selectable
036: * field and an empty footer section.
037: *
038: * @param report a report
039: * @param selectable a selectable object
040: * @return a new group with a header section containing a column field and
041: * an empty footer section
042: */
043: public static Group create(Report report, Selectable selectable) {
044: Group group = new Group(report, selectable);
045:
046: // Create new header section to add to group.
047: Section header = new Section(report);
048:
049: // Create field for selected selectable. Make it bold.
050: Field f = Field.create(null, report, header, selectable
051: .fieldTypeString(), selectable.getId(), true);
052: f.getFormat().setBold(true);
053:
054: // Add field to header section and add section to group.
055: header.addField(f);
056: group.headers().add(header);
057:
058: // Add empty footer section.
059: group.footers().add(new Section(report));
060:
061: return group;
062: }
063:
064: public static String sortOrderIntToString(int order) {
065: return order == SORT_ASCENDING ? "asc" : "desc";
066: }
067:
068: public static int sortOrderStringToInt(String order) {
069: if (order == null || order.length() == 0)
070: return SORT_ASCENDING;
071: return "desc".equals(order.toLowerCase()) ? SORT_DESCENDING
072: : SORT_ASCENDING;
073: }
074:
075: /**
076: * Constructor.
077: *
078: * @param report a report
079: * @param selectable a selectable thingie
080: */
081: public Group(Report report, Selectable selectable) {
082: this .report = report;
083: this .selectable = selectable;
084: sortOrder = SORT_ASCENDING;
085: headers = new SectionArea(SectionArea.GROUP_HEADER);
086: footers = new SectionArea(SectionArea.GROUP_FOOTER);
087: }
088:
089: /**
090: * Returns the selectable used by this group.
091: *
092: * @return a selectable
093: */
094: public Selectable getSelectable() {
095: return selectable;
096: }
097:
098: /**
099: * Sets the selectable used by this group.
100: *
101: * @param newSelectable the new selectable
102: */
103: public void setSelectable(Selectable newSelectable) {
104: selectable = newSelectable;
105: }
106:
107: /**
108: * Reloads reference to selectable.
109: */
110: public void reloadSelectable(DataSource dataSource) {
111: setSelectable(selectable.reloadInstance(dataSource));
112: }
113:
114: public String getSelectableName() {
115: return selectable.getDisplayName();
116: }
117:
118: /**
119: * Returns the sort order (either <code>SORT_ASCENDING</code> or
120: * <code>SORT_DESCENDING</code>).
121: *
122: * @return either <code>SORT_ASCENDING</code> or <code>SORT_DESCENDING</code>
123: */
124: public int getSortOrder() {
125: return sortOrder;
126: }
127:
128: /**
129: * Sets the sort order.
130: *
131: * @param newSortOrder either <code>SORT_ASCENDING</code> or
132: * <code>SORT_DESCENDING</code>
133: */
134: public void setSortOrder(int newSortOrder) {
135: sortOrder = newSortOrder;
136: }
137:
138: /**
139: * Returns the value of this group's selectable. Only valid while
140: * the report is running.
141: *
142: * @return the value of the selectable; undefined when the report
143: * is not running
144: */
145: public Object getValue() {
146: return value;
147: }
148:
149: /**
150: * Sets the group value that is returned by <code>getValue</code>. This
151: * method should only be called by the report while it is running.
152: *
153: * @param report the report from which we retrieve our selectable's value
154: */
155: public void setValue(Report report) {
156: Object val = selectable.getValue(report);
157: if (value == null) {
158: value = val;
159: firstValue = true;
160: newValue = true;
161: } else if (value.equals(val)) {
162: newValue = false;
163: firstValue = false;
164: } else {
165: value = val;
166: newValue = true;
167: firstValue = false;
168: }
169: }
170:
171: public void updateCounter() {
172: if (newValue)
173: recordCount = 1;
174: else
175: ++recordCount;
176: }
177:
178: /**
179: * Returns <code>true</code> when the value of the selectable has
180: * changed.
181: *
182: * @return <code>true</code> when the value of the selectable has
183: * changed
184: */
185: public boolean isNewValue() {
186: return newValue;
187: }
188:
189: /**
190: * Returns the number of records in the group so far.
191: *
192: * @return the number of records in the group so far
193: */
194: public int getRecordCount() {
195: return recordCount;
196: }
197:
198: /**
199: * Layout engines need to call this method when a group's footer is being
200: * output not because this group has a new value but because some previous
201: * group's value changed and we want to output this group's footer.
202: *
203: * @see jimm.datavision.layout.LayoutEngine#groupFooters
204: */
205: public void forceFooterOutput() {
206: newValue = true;
207: }
208:
209: /**
210: * Returns <code>true</code> when this is the first value ever seen
211: * by the group during a report run.
212: *
213: * @return <code>true</code> if this is the first value ever seen
214: */
215: public boolean isFirstValue() {
216: return firstValue;
217: }
218:
219: /**
220: * Returns the headers.
221: *
222: * @return the headers section area
223: */
224: public SectionArea headers() {
225: return headers;
226: }
227:
228: /**
229: * Returns the footers.
230: *
231: * @return the footers section area
232: */
233: public SectionArea footers() {
234: return footers;
235: }
236:
237: /**
238: * Returns <code>true</code> if the specified section is inside this group,
239: * either as a header or a footer.
240: *
241: * @param s a section
242: * @return <code>true</code> if the section is within this group
243: */
244: public boolean contains(Section s) {
245: return headers.contains(s) || footers.contains(s);
246: }
247:
248: /**
249: * Called by a report when it starts running, this method prepares the
250: * group for use.
251: */
252: public void reset() {
253: value = null;
254: newValue = firstValue = true;
255: recordCount = 1;
256: }
257:
258: /**
259: * Writes this group as an XML tag. Asks each section to write itself
260: * as well.
261: *
262: * @param out a writer that knows how to write XML
263: */
264: public void writeXML(XMLWriter out) {
265: out.startElement("group");
266: out.attr("groupable-id", selectable.getId());
267: out.attr("groupable-type", selectable.fieldTypeString());
268: out.attr("sort-order", sortOrderIntToString(sortOrder));
269:
270: ListWriter.writeList(out, headers.sections(), "headers");
271: ListWriter.writeList(out, footers.sections(), "footers");
272:
273: out.endElement();
274: }
275:
276: }
|