0001: package jimm.datavision;
0002:
0003: import jimm.datavision.field.*;
0004: import jimm.datavision.layout.LayoutEngine;
0005: import jimm.datavision.source.*;
0006: import jimm.datavision.source.sql.Database;
0007: import jimm.datavision.gui.sql.DbPasswordDialog;
0008: import jimm.datavision.gui.Designer;
0009: import jimm.datavision.gui.StatusDialog;
0010: import jimm.datavision.gui.parameter.ParamAskWin;
0011: import jimm.util.I18N;
0012: import jimm.util.XMLWriter;
0013: import java.awt.Frame;
0014: import java.io.*;
0015: import java.util.*;
0016: import java.sql.*;
0017: import javax.swing.JOptionPane;
0018: import javax.swing.JFileChooser;
0019: import org.xml.sax.*;
0020: import org.apache.bsf.BSFException;
0021:
0022: /**
0023: * A report holds data source information, accepts parameters from the user,
0024: * runs a query, and uses a layout engine to format the output. It may
0025: * contain many different sections, each of which can contain logic for
0026: * surpressing itself.
0027: *
0028: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
0029: */
0030: public class Report implements Nameable, Writeable {
0031:
0032: /**
0033: * The string to use when reading and writing XML files. Pass it to
0034: * OutputStreamWriters and InputStreamReaders.
0035: */
0036: public static final String XML_JAVA_ENCODING = "UTF8";
0037: /**
0038: * The string to write at the top of an XML file in the XML decl.
0039: */
0040: public static final String XML_ENCODING_ATTRIBUTE = "UTF-8";
0041:
0042: protected static final double OUTPUT_DTD_VERSION = 1.2;
0043:
0044: protected String name;
0045: protected String title;
0046: protected String author;
0047: protected String description;
0048: protected Formula startFormula;
0049: protected DataSource dataSource;
0050: protected HashMap formulas;
0051: protected TreeMap parameters;
0052: protected HashMap usercols;
0053: protected HashMap subreports;
0054: protected ArrayList groups;
0055: protected SectionArea reportHeaders;
0056: protected SectionArea reportFooters;
0057: protected SectionArea pageHeaders;
0058: protected SectionArea pageFooters;
0059: protected SectionArea details;
0060: protected DataCursor rset;
0061: protected LayoutEngine layoutEngine;
0062: protected PaperFormat paperFormat;
0063: protected Collection aggregateFields;
0064: protected String databasePassword;
0065: protected boolean askedForParameters;
0066: protected boolean parametersHaveValues;
0067: protected boolean paramsSetManually;
0068: protected ParameterReader paramReader;
0069: /** Flag for Database data sources. */
0070: protected boolean caseSensitiveDatabaseNames;
0071: protected Scripting scripting;
0072: /**
0073: * This field holds default format, border, and bounds values for all fields.
0074: * For all format ivars, if the value of the ivar is null then the value is
0075: * obtained from this default field's format.
0076: */
0077: protected Field defaultField;
0078:
0079: /**
0080: * Constructs an empty report.
0081: */
0082: public Report() {
0083: formulas = new HashMap();
0084: parameters = new TreeMap();
0085: usercols = new HashMap();
0086: subreports = new HashMap();
0087: groups = new ArrayList();
0088: name = I18N.get("Report.default_name");
0089: title = I18N.get("Report.default_title");
0090: askedForParameters = false;
0091: parametersHaveValues = false;
0092: paramsSetManually = false;
0093: caseSensitiveDatabaseNames = true;
0094: paperFormat = PaperFormat.getDefault();
0095: scripting = new Scripting(this );
0096:
0097: defaultField = Field.create(new Long(0), this , null, "text",
0098: I18N.get("Report.default_field_name"), true);
0099: defaultField.setFormat(Format.createDefaultFormat());
0100: defaultField.setBorder(new Border(defaultField));
0101:
0102: initializeSections();
0103: }
0104:
0105: public void initializeSections() {
0106: reportHeaders = new SectionArea(SectionArea.REPORT_HEADER);
0107: reportFooters = new SectionArea(SectionArea.REPORT_FOOTER);
0108: pageHeaders = new SectionArea(SectionArea.PAGE_HEADER);
0109: pageFooters = new SectionArea(SectionArea.PAGE_FOOTER);
0110: details = new SectionArea(SectionArea.DETAIL);
0111:
0112: reportHeaders.add(new Section(this ));
0113: pageHeaders.add(new Section(this ));
0114: pageFooters.add(new Section(this ));
0115: reportFooters.add(new Section(this ));
0116: details.add(new Section(this ));
0117: }
0118:
0119: /**
0120: * Sets the layout engine to use.
0121: *
0122: * @param layoutEngine a layout engine
0123: */
0124: public void setLayoutEngine(LayoutEngine layoutEngine) {
0125: this .layoutEngine = layoutEngine;
0126: this .layoutEngine.setReport(this );
0127: }
0128:
0129: /**
0130: * Generates and returns a new unique id number. The number is one larger
0131: * than the largest in a given list of {@link Identity} objects whose
0132: * identifiers must be <code>Long</code> objects.
0133: *
0134: * @param iter an iterator over a collection if <code>Identity</code>
0135: * objects whose identifiers must be <code>Long</code>s
0136: * @return a <code>Long</code>
0137: */
0138: protected Long generateNewId(Iterator iter) {
0139: long max = 0;
0140: while (iter.hasNext()) {
0141: Object id = ((Identity) iter.next()).getId();
0142: long longVal = ((Long) id).longValue();
0143: if (longVal > max)
0144: max = longVal;
0145: }
0146: return new Long(max + 1);
0147: }
0148:
0149: /**
0150: * Generates and returns a new unique formula id number.
0151: *
0152: * @return a long id
0153: */
0154: public Long generateNewFormulaId() {
0155: return generateNewId(formulas.values().iterator());
0156: }
0157:
0158: /**
0159: * Generates and returns a new unique parameter id number.
0160: *
0161: * @return a long id
0162: */
0163: public Long generateNewParameterId() {
0164: return generateNewId(parameters.values().iterator());
0165: }
0166:
0167: /**
0168: * Generates and returns a new unique user column id number.
0169: *
0170: * @return a long id
0171: */
0172: public Long generateNewUserColumnId() {
0173: return generateNewId(usercols.values().iterator());
0174: }
0175:
0176: /**
0177: * Generates and returns a new unique user column id number.
0178: *
0179: * @return a long id
0180: */
0181: public Long generateNewSubreportId() {
0182: return generateNewId(subreports.values().iterator());
0183: }
0184:
0185: /**
0186: * Given an id, returns the field that has that id. If no field with the
0187: * specified id exists, returns <code>null</code>.
0188: *
0189: * @return a field, or <code>null</code> if no field with the specified
0190: * id exists
0191: */
0192: public Field findField(final Object id) {
0193: final Field listOfOne[] = new Field[1];
0194: listOfOne[0] = null;
0195: withSectionsDo(new SectionWalker() {
0196: public void step(Section s) {
0197: Field f = s.findField(id);
0198: if (f != null)
0199: listOfOne[0] = f;
0200: }
0201: });
0202: return listOfOne[0];
0203: }
0204:
0205: public String getName() {
0206: return name;
0207: }
0208:
0209: public void setName(String newName) {
0210: name = newName;
0211: }
0212:
0213: public String getTitle() {
0214: return title;
0215: }
0216:
0217: public void setTitle(String newTitle) {
0218: title = newTitle;
0219: }
0220:
0221: public String getAuthor() {
0222: return author;
0223: }
0224:
0225: public void setAuthor(String newAuthor) {
0226: author = newAuthor;
0227: }
0228:
0229: public String getDescription() {
0230: return description;
0231: }
0232:
0233: public void setDescription(String newDescription) {
0234: description = newDescription;
0235: }
0236:
0237: /**
0238: * Returns the report's start formula; may be <code>null</code>.
0239: *
0240: * @return the report's start formula; may be <code>null</code>
0241: */
0242: public Formula getStartFormula() {
0243: return startFormula;
0244: }
0245:
0246: public void setStartFormula(Formula newStartFormula) {
0247: startFormula = newStartFormula;
0248: }
0249:
0250: public Scripting getScripting() {
0251: return scripting;
0252: }
0253:
0254: /**
0255: * Evaluates an <var>evalString</var> using <var>language</var> and returns
0256: * the results. Called by {@link Formula#evaluate} after it has created the
0257: * <var>evalString</var>.
0258: *
0259: * @param language the language to use
0260: * @param evalString the string to evaluate
0261: * @param displayName a name (for example, a formula name) to display with
0262: * error messages
0263: * @return the result
0264: */
0265: public Object eval(String language, String evalString,
0266: String displayName) throws BSFException {
0267: return scripting.eval(language, evalString, displayName);
0268: }
0269:
0270: /**
0271: * Returns the value of the object ({@link Column}, {@link Formula},
0272: * {@link Parameter}, {@link UserColumn}, or {@link SpecialField})
0273: * identified by <var>labelOrId</var>.
0274: *
0275: * @param labelOrId the label or id of a
0276: */
0277: public Object value(String labelOrId) {
0278: if (labelOrId == null)
0279: return null;
0280:
0281: labelOrId = labelOrId.trim();
0282: if (labelOrId.length() == 0)
0283: return null;
0284:
0285: if (labelOrId.charAt(0) == '{') {
0286: if (labelOrId.length() == 1)
0287: return null;
0288: int endPos = labelOrId.indexOf('}');
0289: switch (labelOrId.charAt(1)) {
0290: case '@':
0291: Formula f = findFormulaByName(labelOrId.substring(2,
0292: endPos));
0293: if (f == null)
0294: return null;
0295: return f.evaluate(null); // TODO: can we pass in field?
0296: case '?':
0297: Parameter p = findParameterByName(labelOrId.substring(
0298: 2, endPos));
0299: if (p == null)
0300: return null;
0301: return p.getValue();
0302: case '%':
0303: // TODO: first arg is field; can we pass it in?
0304: return SpecialField.value(null, labelOrId.substring(2,
0305: endPos), this );
0306: case '!':
0307: UserColumn uc = findUserColumnByName(labelOrId
0308: .substring(2, endPos));
0309: if (uc == null)
0310: return null;
0311: return uc.getValue(this );
0312: }
0313: }
0314:
0315: if (labelOrId.startsWith("{") && labelOrId.endsWith("}"))
0316: labelOrId = labelOrId.substring(1, labelOrId.length() - 1);
0317: Column col = findColumn(labelOrId);
0318: if (col == null)
0319: return null;
0320: else
0321: return columnValue(col);
0322: }
0323:
0324: /**
0325: * Returns the field that holds default format, border, and bounds values for
0326: * all fields. For all format ivars, if the value of the ivar is null then the
0327: * value is obtained from this default field's format. If you need those
0328: * values, clone them before using them.
0329: *
0330: * @return the default field
0331: */
0332: public Field getDefaultField() {
0333: return defaultField;
0334: }
0335:
0336: public DataSource getDataSource() {
0337: return dataSource;
0338: }
0339:
0340: public boolean hasDataSource() {
0341: return dataSource != null;
0342: }
0343:
0344: /**
0345: * Sets the data source. Called by {@link ReportReader#database}, {@link
0346: * ReportReader#charSepSource}, or user code.
0347: * <p>
0348: * If we already have a data source (for example, someone has called {@link
0349: * #setDatabaseConnection}), the existing data source will be stomped on. Both
0350: * {@link ReportReader#database} and {@link ReportReader#charSepSource} call
0351: * {@link #hasDataSource} to check first.
0352: *
0353: * @param newDataSource a new data source
0354: */
0355: public void setDataSource(DataSource newDataSource) {
0356: dataSource = newDataSource;
0357: }
0358:
0359: /**
0360: * Sets the database connection. If this is called before reading a
0361: * report XML file, then this connection will be used instead of
0362: * the connection information specified in the report.
0363: * <p>
0364: * <em>Note:</em> this connection will <em>not</em> be closed when
0365: * the report finishes or even if the database object's connection
0366: * is reset.
0367: *
0368: * @param conn a database connection
0369: */
0370: public void setDatabaseConnection(Connection conn)
0371: throws SQLException {
0372: dataSource = new Database(conn, this );
0373: databasePassword = ""; // So user won't be asked for password
0374: }
0375:
0376: /**
0377: * Returns the value of the <var>caseSensitiveDatabaseNames</var> flag.
0378: * By default, this flag is <code>true</code>.
0379: *
0380: * @return <code>true</code> if all mixed-case names should be quoted when
0381: * appropriate
0382: */
0383: public boolean caseSensitiveDatabaseNames() {
0384: return caseSensitiveDatabaseNames;
0385: }
0386:
0387: /**
0388: * Sets the value of <var>caseSensitiveDatabaseNames</var>.
0389: * Normally, any database data sources' query objects quote all mixed-case
0390: * names where appropriate.
0391: */
0392: public void setCaseSensitiveDatabaseNames(boolean val) {
0393: caseSensitiveDatabaseNames = val;
0394: }
0395:
0396: /**
0397: * Tells this report to reload all references to column objects. Called
0398: * by a database when it resets its connection.
0399: *
0400: * @see Database#reset
0401: */
0402: public void reloadColumns() {
0403: for (Iterator iter = groups(); iter.hasNext();) {
0404: Group g = (Group) iter.next();
0405: g.reloadSelectable(dataSource);
0406: }
0407: withFieldsDo(new FieldWalker() {
0408: public void step(Field f) {
0409: if (f instanceof ColumnField) {
0410: ColumnField cf = (ColumnField) f;
0411: cf.setColumn(dataSource.findColumn(cf.getColumn()
0412: .getId()));
0413: }
0414: }
0415: });
0416:
0417: // Let the data source tell its ancillary objects (such as the query)
0418: // to reload its columns.
0419: dataSource.reloadColumns();
0420: }
0421:
0422: /**
0423: * Given an id (a column name), returns the column that has that id. If no
0424: * column with the specified id exists, returns <code>null</code>. Calls
0425: * {@link DataSource#findColumn}.
0426: *
0427: * @return a column, or <code>null</code> if no column with the specified
0428: * id exists
0429: */
0430: public Column findColumn(String id) {
0431: return dataSource.findColumn(id);
0432: }
0433:
0434: public PaperFormat getPaperFormat() {
0435: return paperFormat;
0436: }
0437:
0438: public void setPaperFormat(PaperFormat newPaperFormat) {
0439: paperFormat = newPaperFormat;
0440: }
0441:
0442: /**
0443: * Figure out what <var>obj</var> is and add it.
0444: */
0445: public void add(Object obj) {
0446: if (obj instanceof Parameter)
0447: addParameter((Parameter) obj);
0448: else if (obj instanceof Formula)
0449: addFormula((Formula) obj);
0450: else if (obj instanceof UserColumn)
0451: addUserColumn((UserColumn) obj);
0452: else if (obj instanceof Group)
0453: addGroup((Group) obj);
0454: else
0455: // Shouldn't happen
0456: ErrorHandler.error(I18N.get("Report.add_err_1") + ' '
0457: + obj.getClass().getName() + ' '
0458: + I18N.get("Report.add_err_2"));
0459: }
0460:
0461: /**
0462: * Figure out what <var>obj</var> is and remove it.
0463: */
0464: public void remove(Object obj) {
0465: if (obj instanceof Field)
0466: removeField((Field) obj);
0467: else if (obj instanceof Formula)
0468: removeFormula((Formula) obj);
0469: else if (obj instanceof Parameter)
0470: removeParameter((Parameter) obj);
0471: else if (obj instanceof UserColumn)
0472: removeUserColumn((UserColumn) obj);
0473: else if (obj instanceof Group)
0474: removeGroup((Group) obj);
0475: else if (obj instanceof Section)
0476: removeSection((Section) obj);
0477: else
0478: ErrorHandler.error(I18N.get("Report.remove_err_1") + ' '
0479: + obj.getClass().getName()
0480: + I18N.get("Report.remove_err_2"));
0481: }
0482:
0483: // ---------------- parameters
0484:
0485: /**
0486: * Returns the parameter with the specified id or <code>null</code> if one is
0487: * not found. <em>Note</em>: don't use this method if you need a parameter's
0488: * value. That value is supplied by the user. Call {@link
0489: * Report#getParameterValue} instead, which asks the user to supply the value.
0490: *
0491: * @param id the parameter id
0492: * @return the parameter with that id or <code>null</code> if one is not found
0493: */
0494: public Parameter findParameter(Object id) {
0495: if (id instanceof String)
0496: id = new Long((String) id);
0497: return (Parameter) parameters.get(id);
0498: }
0499:
0500: /**
0501: * Returns the parameter with the specified name or <code>null</code>
0502: * if one is not found.
0503: *
0504: * @param name the name string
0505: * @return the parameter with that name or <code>null</code> if one is not
0506: * found
0507: */
0508: public Parameter findParameterByName(String name) {
0509: if (name == null || name.length() == 0)
0510: return null;
0511:
0512: name = name.toLowerCase();
0513: for (Iterator iter = parameters.values().iterator(); iter
0514: .hasNext();) {
0515: Parameter p = (Parameter) iter.next();
0516: if (name.equals(p.getName().toLowerCase()))
0517: return p;
0518: }
0519: return null;
0520: }
0521:
0522: public void addParameter(Parameter p) {
0523: parameters.put(p.getId(), p);
0524: }
0525:
0526: public void removeParameter(Parameter p) {
0527: parameters.remove(p.getId());
0528: }
0529:
0530: public Iterator parameters() {
0531: return parameters.values().iterator();
0532: }
0533:
0534: // ---------------- formulas
0535:
0536: /**
0537: * Returns the formula with the specified id or <code>null</code>
0538: * if one is not found.
0539: *
0540: * @param id the formula id
0541: * @return the formula with that id or <code>null</code> if one is not found
0542: */
0543: public Formula findFormula(Object id) {
0544: if (id instanceof String)
0545: id = new Long((String) id);
0546: return (Formula) formulas.get(id);
0547: }
0548:
0549: /**
0550: * Returns the formula with the specified name or <code>null</code>
0551: * if one is not found.
0552: *
0553: * @param name the name string
0554: * @return the formula with that name or <code>null</code> if one is not found
0555: */
0556: public Formula findFormulaByName(String name) {
0557: if (name == null || name.length() == 0)
0558: return null;
0559:
0560: name = name.toLowerCase();
0561: for (Iterator iter = formulas.values().iterator(); iter
0562: .hasNext();) {
0563: Formula f = (Formula) iter.next();
0564: if (name.equals(f.getName().toLowerCase()))
0565: return f;
0566: }
0567: return null;
0568: }
0569:
0570: public void addFormula(Formula f) {
0571: formulas.put(f.getId(), f);
0572: }
0573:
0574: public void removeFormula(Formula f) {
0575: formulas.remove(f.getId());
0576: }
0577:
0578: public Iterator formulas() {
0579: return formulas.values().iterator();
0580: }
0581:
0582: // ---------------- user columns
0583:
0584: /**
0585: * Returns the user column with the specified id or <code>null</code>
0586: * if one is not found.
0587: *
0588: * @param id the user column id
0589: * @return the user column with that id or <code>null</code> if one
0590: * is not found
0591: */
0592: public UserColumn findUserColumn(Object id) {
0593: if (id instanceof String)
0594: id = new Long((String) id);
0595: return (UserColumn) usercols.get(id);
0596: }
0597:
0598: /**
0599: * Returns the user column with the specified name or <code>null</code>
0600: * if one is not found.
0601: *
0602: * @param name the name string
0603: * @return the user column with that name or <code>null</code> if one
0604: * is not found
0605: */
0606: public UserColumn findUserColumnByName(String name) {
0607: if (name == null || name.length() == 0)
0608: return null;
0609:
0610: name = name.toLowerCase();
0611: for (Iterator iter = usercols.values().iterator(); iter
0612: .hasNext();) {
0613: UserColumn f = (UserColumn) iter.next();
0614: if (name.equals(f.getName().toLowerCase()))
0615: return f;
0616: }
0617: return null;
0618: }
0619:
0620: public void addUserColumn(UserColumn uc) {
0621: usercols.put(uc.getId(), uc);
0622: }
0623:
0624: public void removeUserColumn(UserColumn uc) {
0625: usercols.remove(uc.getId());
0626: }
0627:
0628: public Iterator userColumns() {
0629: return usercols.values().iterator();
0630: }
0631:
0632: // ---------------- subreports
0633:
0634: /**
0635: * Returns the subreport with the specified id or <code>null</code>
0636: * if one is not found.
0637: *
0638: * @param id the subreport id
0639: * @return the subreport with that id or <code>null</code> if one is not found
0640: */
0641: public Subreport findSubreport(Object id) {
0642: if (id instanceof String)
0643: id = new Long((String) id);
0644: return (Subreport) subreports.get(id);
0645: }
0646:
0647: public void addSubreport(Subreport sub) {
0648: subreports.put(sub.getId(), sub);
0649: }
0650:
0651: public void removeSubreport(Subreport sub) {
0652: subreports.remove(sub.getId());
0653: }
0654:
0655: public Iterator subreports() {
0656: return subreports.values().iterator();
0657: }
0658:
0659: // ---------------- selectable methods
0660:
0661: public Selectable findSelectable(Object id, String type) {
0662: if ("column".equals(type))
0663: return findColumn(id.toString());
0664: else
0665: // "usercol"
0666: return findUserColumn(id);
0667: }
0668:
0669: // ---------------- section manipulation
0670:
0671: /**
0672: * Returns the section area corresponding to <var>area</var>, but
0673: * <em>only</em> if <var>area</var> is not group header or group footer. Both
0674: * of those possibly apply to multiple groups, so we can't tell which one is
0675: * desired.
0676: *
0677: * @param area one of the <code>SectionArea.*</code> constants
0678: * @return the section area corresponding to <var>area</var>
0679: */
0680: public SectionArea getSectionArea(int area) {
0681: switch (area) {
0682: case SectionArea.REPORT_HEADER:
0683: return reportHeaders;
0684: case SectionArea.REPORT_FOOTER:
0685: return reportFooters;
0686: case SectionArea.PAGE_HEADER:
0687: return pageHeaders;
0688: case SectionArea.PAGE_FOOTER:
0689: return pageFooters;
0690: case SectionArea.DETAIL:
0691: return details;
0692: default:
0693: return null;
0694: }
0695: }
0696:
0697: /**
0698: * Returns <code>true</code> if this report contains the specified section.
0699: *
0700: * @return <code>true</code> if this report contains the specified section
0701: */
0702: public boolean contains(Section s) {
0703: return s.getArea() != null;
0704: }
0705:
0706: /**
0707: * Returns the first section in the list of the specified type. If the
0708: * type is <code>GROUP_HEADER</code> or <code>GROUP_FOOTER</code>, return
0709: * (a) <code>null</code> if there are no groups in the report or
0710: * (b) the first header or footer of the first group.
0711: * <p>
0712: * Used by {@link jimm.datavision.gui.cmd.FieldClipping} when trying to paste
0713: * a field into either some other report or the same report if the original
0714: * section is no longer in that report.
0715: *
0716: * @return a section; may be <code>null</code>
0717: */
0718: public Section getFirstSectionByArea(int area) {
0719: switch (area) {
0720: case SectionArea.GROUP_HEADER:
0721: if (groups.isEmpty())
0722: return null;
0723: return ((Group) groups.get(0)).headers().first();
0724: case SectionArea.GROUP_FOOTER:
0725: if (groups.isEmpty())
0726: return null;
0727: return ((Group) groups.get(0)).footers().first();
0728: default:
0729: return getSectionArea(area).first();
0730: }
0731: }
0732:
0733: /**
0734: * Returns a structure useful only by this report for re-inserting a section.
0735: * This structure may later be passed to {@link #reinsertSection}. Used by
0736: * {@link jimm.datavision.gui.cmd.DeleteSectionCommand}.
0737: * <p>
0738: * We assume that <var>s</var> is contained within some {@link SectionArea}.
0739: *
0740: * @param s the section in question
0741: * @return section location information
0742: */
0743: public ReportSectionLoc getSectionLocation(Section s) {
0744: SectionArea area = s.getArea();
0745: return new ReportSectionLoc(s, area, area.indexOf(s));
0746: }
0747:
0748: /**
0749: * Reinserts a section based on the location information previously retrieved
0750: * by a call to {@link #getSectionLocation}. Used by {@link
0751: * jimm.datavision.gui.cmd.DeleteSectionCommand}.
0752: *
0753: * @param loc a section locatction
0754: */
0755: public void reinsertSection(ReportSectionLoc loc) {
0756: loc.area.add(loc.index, loc.section);
0757: }
0758:
0759: // ---------------- section areas
0760:
0761: public SectionArea headers() {
0762: return reportHeaders;
0763: }
0764:
0765: public SectionArea footers() {
0766: return reportFooters;
0767: }
0768:
0769: public SectionArea pageHeaders() {
0770: return pageHeaders;
0771: }
0772:
0773: public SectionArea pageFooters() {
0774: return pageFooters;
0775: }
0776:
0777: public SectionArea details() {
0778: return details;
0779: }
0780:
0781: // ---------------- groups
0782:
0783: public void addGroup(Group g) {
0784: groups.add(g);
0785: dataSource.removeSort(g.getSelectable());
0786: }
0787:
0788: public void removeGroup(Group g) {
0789: groups.remove(g);
0790: }
0791:
0792: public void removeAllGroups() {
0793: groups.clear();
0794: }
0795:
0796: public Iterator groups() {
0797: return groups.iterator();
0798: }
0799:
0800: public int countGroups() {
0801: return groups.size();
0802: }
0803:
0804: public boolean hasGroups() {
0805: return countGroups() > 0;
0806: }
0807:
0808: /**
0809: * Returns the last (innermost) group in the report, or <code>null</code>
0810: * if there are no groups.
0811: *
0812: * @return the last (innermost) group in the report, or <code>null</code>
0813: * if there are no groups.
0814: */
0815: public Group innermostGroup() {
0816: return groups.size() > 0 ? (Group) groups
0817: .get(groups.size() - 1) : null;
0818: }
0819:
0820: /**
0821: * Returns an iterator over the groups in reverse order. Useful when
0822: * displaying group footers.
0823: *
0824: * @return an iterator over the groups, in reverse order
0825: */
0826: public Iterator groupsReversed() {
0827: ArrayList reversed = (ArrayList) groups.clone();
0828: Collections.reverse(reversed);
0829: return reversed.iterator();
0830: }
0831:
0832: public void removeField(Field f) {
0833: Section s = sectionContaining(f);
0834: if (s != null)
0835: s.removeField(f);
0836: }
0837:
0838: /**
0839: * Returns <code>true</code> if the specified field exists within this
0840: * report.
0841: *
0842: * @param f a field
0843: * @return <code>true</code> if the specified field exists within this
0844: * report
0845: */
0846: public boolean contains(Field f) {
0847: return sectionContaining(f) != null;
0848: }
0849:
0850: /**
0851: * Returns the section containing the specified field, or <code>null</code>
0852: * if the field is not in the report.
0853: *
0854: * @param f a field
0855: * @return the section containing the specified field, or <code>null</code>
0856: * if the field is not in the report
0857: */
0858: public Section sectionContaining(Field f) {
0859: Section s;
0860: Iterator iter, iter2;
0861: for (iter = reportHeaders.iterator(); iter.hasNext();) {
0862: s = (Section) iter.next();
0863: if (s.contains(f))
0864: return s;
0865: }
0866: for (iter = pageHeaders.iterator(); iter.hasNext();) {
0867: s = (Section) iter.next();
0868: if (s.contains(f))
0869: return s;
0870: }
0871: for (iter = groups.iterator(); iter.hasNext();) {
0872: for (iter2 = ((Group) iter.next()).headers().iterator(); iter2
0873: .hasNext();) {
0874: s = (Section) iter2.next();
0875: if (s.contains(f))
0876: return s;
0877: }
0878: }
0879: for (iter = details.iterator(); iter.hasNext();) {
0880: s = (Section) iter.next();
0881: if (s.contains(f))
0882: return s;
0883: }
0884: for (iter = groups.iterator(); iter.hasNext();) {
0885: for (iter2 = ((Group) iter.next()).footers().iterator(); iter2
0886: .hasNext();) {
0887: s = (Section) iter2.next();
0888: if (s.contains(f))
0889: return s;
0890: }
0891: }
0892: for (iter = reportFooters.iterator(); iter.hasNext();) {
0893: s = (Section) iter.next();
0894: if (s.contains(f))
0895: return s;
0896: }
0897: for (iter = pageFooters.iterator(); iter.hasNext();) {
0898: s = (Section) iter.next();
0899: if (s.contains(f))
0900: return s;
0901: }
0902:
0903: return null;
0904: }
0905:
0906: /**
0907: * Returns <code>true</code> if the specified field exists within this
0908: * report either directly (as a field) or indirectly (as a formula used
0909: * by a aggregate, parameter, user column, or another formula).
0910: *
0911: * @param f a field
0912: * @return <code>true</code> if the specified field exists within this
0913: * report
0914: */
0915: public boolean containsReferenceTo(final Field f) {
0916: if (startFormula != null && startFormula.refersTo(f))
0917: return true;
0918:
0919: final boolean[] answer = new boolean[1];
0920: answer[0] = false;
0921:
0922: withSectionsDo(new SectionWalker() {
0923: public void step(Section s) {
0924: if (s.containsReferenceTo(f))
0925: answer[0] = true;
0926: }
0927: });
0928:
0929: return answer[0];
0930: }
0931:
0932: /**
0933: * Returns <code>true</code> if the specified formula exists within this
0934: * report either directly (as a formula field) or indirectly (as a formula
0935: * used by a aggregate or by another formula). This is not the same as
0936: * answering the question, "Does this report contain this formula?" because
0937: * we want to know if the visual portion of the report or some other formula
0938: * accesses the specified formula.
0939: *
0940: * @param f a formula
0941: * @return <code>true</code> if the specified parameter exists within some
0942: * visual element of the report or within some formula
0943: */
0944: public boolean containsReferenceTo(final Formula f) {
0945: if (startFormula != null && startFormula.refersTo(f))
0946: return true;
0947:
0948: final boolean[] answer = new boolean[1];
0949: answer[0] = false;
0950:
0951: withSectionsDo(new SectionWalker() {
0952: public void step(Section s) {
0953: if (s.containsReferenceTo(f))
0954: answer[0] = true;
0955: }
0956: });
0957:
0958: return answer[0];
0959: }
0960:
0961: /**
0962: * Returns <code>true</code> if the specified user column exists within
0963: * this report either directly (as a user column field) or indirectly
0964: * (inside a aggregate or formula). This is not the same as answering the
0965: * question, "Does this report contain this user column?" because we want
0966: * to know if the visual portion of the report or some formula accesses the
0967: * specified user column.
0968: *
0969: * @param uc a user column
0970: * @return <code>true</code> if the specified parameter exists within some
0971: * visual element of the report or within some formula
0972: */
0973: public boolean containsReferenceTo(final UserColumn uc) {
0974: if (startFormula != null && startFormula.refersTo(uc))
0975: return true;
0976:
0977: final boolean[] answer = new boolean[1];
0978: answer[0] = false;
0979:
0980: withSectionsDo(new SectionWalker() {
0981: public void step(Section s) {
0982: if (s.containsReferenceTo(uc))
0983: answer[0] = true;
0984: }
0985: });
0986:
0987: return answer[0];
0988: }
0989:
0990: /**
0991: * Returns <code>true</code> if the specified parameter exists within this
0992: * report either directly (as a parameter field) or indirectly (as a
0993: * parameter used by a aggregate or by another parameter or in the query's
0994: * where clause). This is not the same as answering the question, "Does
0995: * this report contain this parameter?" because we want to know if the
0996: * visual portion of the report or some formula accesses the specified
0997: * parameter.
0998: *
0999: * @param p a parameter
1000: * @return <code>true</code> if the specified parameter exists within some
1001: * visual element of the report or within some formula or within the
1002: * query's where clause
1003: */
1004: public boolean containsReferenceTo(final Parameter p) {
1005: if (startFormula != null && startFormula.refersTo(p))
1006: return true;
1007:
1008: if (dataSource.containsReferenceTo(p))
1009: return true;
1010:
1011: final boolean[] answer = new boolean[1];
1012: answer[0] = false;
1013:
1014: withSectionsDo(new SectionWalker() {
1015: public void step(Section s) {
1016: if (s.containsReferenceTo(p))
1017: answer[0] = true;
1018: }
1019: });
1020:
1021: return answer[0];
1022: }
1023:
1024: /**
1025: * Returns a list of all the parameters actually used in the report.
1026: *
1027: * @return a list of parameters
1028: */
1029: protected List collectUsedParameters() {
1030: ArrayList list = new ArrayList();
1031: for (Iterator iter = parameters.values().iterator(); iter
1032: .hasNext();) {
1033: Parameter p = (Parameter) iter.next();
1034: if (containsReferenceTo(p))
1035: list.add(p);
1036: }
1037: return list;
1038: }
1039:
1040: /**
1041: * Returns the group associated with the specified column, or
1042: * <code>null</code> if there isn't one.
1043: *
1044: * @param selectable a selectable field
1045: * @return the group associated with this column, or <code>null</code>
1046: * if there isn't one
1047: */
1048: public Group findGroup(Selectable selectable) {
1049: for (Iterator iter = groups(); iter.hasNext();) {
1050: Group g = (Group) iter.next();
1051: if (g.getSelectable() == selectable)
1052: return g;
1053: }
1054: return null;
1055: }
1056:
1057: /**
1058: * Returns the group associated with the specified section, or
1059: * <code>null</code> if there isn't one.
1060: *
1061: * @param section a section
1062: * @return the group associated with this section, or <code>null</code>
1063: * if there isn't one
1064: */
1065: public Group findGroup(Section section) {
1066: for (Iterator iter = groups(); iter.hasNext();) {
1067: Group group = (Group) iter.next();
1068: if (group.contains(section))
1069: return group;
1070: }
1071: return null;
1072: }
1073:
1074: /**
1075: * Returns <code>true</code> if the specified section is contained in
1076: * any group. The section may be either a header or a footer section.
1077: *
1078: * @param section a section
1079: * @return <code>true</code> if the section is within some group
1080: */
1081: public boolean isInsideGroup(Section section) {
1082: return findGroup(section) != null;
1083: }
1084:
1085: /**
1086: * Returns <code>true</code> if the specified data source column is a
1087: * group column.
1088: *
1089: * @return <code>true</code> if the specified data source column is a
1090: * group column
1091: */
1092: public boolean isUsedBySomeGroup(Selectable g) {
1093: return findGroup(g) != null;
1094: }
1095:
1096: /**
1097: * Creates and returns a new section below the specified one.
1098: *
1099: * @param goBelowThis a section
1100: * @return a new section
1101: */
1102: public Section insertSectionBelow(Section goBelowThis) {
1103: return insertSectionBelow(null, goBelowThis);
1104: }
1105:
1106: /**
1107: * Inserts a (possibly newly created) section below the specified one.
1108: * Returns the inserted section.
1109: *
1110: * @param section the section to insert
1111: * @param goBelowThis <var>section</var> goes below this one;
1112: * @return a new section
1113: */
1114: public Section insertSectionBelow(Section section,
1115: Section goBelowThis) {
1116: if (section == null)
1117: section = new Section(this );
1118:
1119: if (goBelowThis == null)
1120: return reportHeaders.insertAfter(section, null);
1121:
1122: if (reportHeaders.contains(goBelowThis))
1123: return reportHeaders.insertAfter(section, goBelowThis);
1124: else if (reportFooters.contains(goBelowThis))
1125: return reportFooters.insertAfter(section, goBelowThis);
1126: else if (pageHeaders.contains(goBelowThis))
1127: return pageHeaders.insertAfter(section, goBelowThis);
1128: else if (pageFooters.contains(goBelowThis))
1129: return pageFooters.insertAfter(section, goBelowThis);
1130: else if (details.contains(goBelowThis))
1131: return details.insertAfter(section, goBelowThis);
1132:
1133: for (Iterator iter = groups(); iter.hasNext();) {
1134: Group g = (Group) iter.next();
1135: if (g.headers().contains(goBelowThis))
1136: return g.headers().insertAfter(section, goBelowThis);
1137: else if (g.footers().contains(goBelowThis))
1138: return g.footers().insertAfter(section, goBelowThis);
1139: }
1140:
1141: return null; // Should not happen
1142: }
1143:
1144: /**
1145: * Removes the specified section.
1146: *
1147: * @param s a section
1148: */
1149: public void removeSection(Section s) {
1150: if (reportHeaders.contains(s))
1151: reportHeaders.remove(s);
1152: else if (reportFooters.contains(s))
1153: reportFooters.remove(s);
1154: else if (pageHeaders.contains(s))
1155: pageHeaders.remove(s);
1156: else if (pageFooters.contains(s))
1157: pageFooters.remove(s);
1158: else if (details.contains(s))
1159: details.remove(s);
1160: else {
1161: for (Iterator iter = groups(); iter.hasNext();) {
1162: Group g = (Group) iter.next();
1163: if (g.headers().contains(s)) {
1164: g.headers().remove(s);
1165: return;
1166: } else if (g.footers().contains(s)) {
1167: g.footers().remove(s);
1168: return;
1169: }
1170: }
1171: }
1172: }
1173:
1174: /**
1175: * Returns <code>true</code> if this report contains some field anywhere.
1176: * Useful when the GUI is enabling menu items, for example.
1177: *
1178: * @return <code>true</code> if this report contains some field anywhere
1179: */
1180: public boolean hasFields() {
1181: final boolean[] answer = new boolean[1];
1182: answer[0] = false;
1183: withFieldsDo(new FieldWalker() {
1184: public void step(Field f) {
1185: answer[0] = true;
1186: }
1187: });
1188: return answer[0];
1189: }
1190:
1191: /**
1192: * Returns <code>true</code> if this report contains some parameter field
1193: * anywhere. This doesn't determine if any parameters have been created,
1194: * but rather if any of the parameters are actually used in the report.
1195: * <p>
1196: * This method is not used by DataVision, but is useful for apps embedding
1197: * DataVision that want to answer the question, "Do I have to read in
1198: * a parameter XML file?"
1199: *
1200: * @return <code>true</code> if this report contains some parameter field
1201: * anywhere
1202: */
1203: public boolean hasParameterFields() {
1204: final boolean answer[] = new boolean[1];
1205: answer[0] = false;
1206: withFieldsDo(new FieldWalker() {
1207: public void step(Field f) {
1208: if (f instanceof ParameterField)
1209: answer[0] = true;
1210: }
1211: });
1212: return answer[0];
1213: }
1214:
1215: /**
1216: * Returns <code>true</code> if the specified section is the only section
1217: * of its kind; that is, the only section in the collection in which it
1218: * is contained.
1219: *
1220: * @param s a report section
1221: * @return <code>true</code> if this section is a loner
1222: */
1223: public boolean isOneOfAKind(Section s) {
1224: if (reportHeaders.contains(s))
1225: return reportHeaders.size() == 1;
1226: if (reportFooters.contains(s))
1227: return reportFooters.size() == 1;
1228: if (pageHeaders.contains(s))
1229: return pageHeaders.size() == 1;
1230: if (pageFooters.contains(s))
1231: return pageFooters.size() == 1;
1232: if (details.contains(s))
1233: return details.size() == 1;
1234:
1235: for (Iterator iter = groups(); iter.hasNext();) {
1236: Group g = (Group) iter.next();
1237: if (g.headers().contains(s))
1238: return g.headers().size() == 1;
1239: if (g.footers().contains(s))
1240: return g.footers().size() == 1;
1241: }
1242:
1243: return false;
1244: }
1245:
1246: /**
1247: * Sets the database password.
1248: */
1249: public void setDatabasePassword(String pwd) {
1250: databasePassword = pwd;
1251: }
1252:
1253: /**
1254: * Sets a database's user name and password. Called from {@link
1255: * Database#initializeConnection}. If we already have the password, give it to
1256: * the database. If we don't, ask the user for both the user name (supplied to
1257: * us) and the password and give them to the database.
1258: *
1259: * @param db the database
1260: */
1261: public void askForPassword(Database db) {
1262: if (databasePassword != null) {
1263: db.setPassword(databasePassword);
1264: return;
1265: }
1266:
1267: if (ErrorHandler.usingGUI()) {
1268: DbPasswordDialog dialog = new DbPasswordDialog(
1269: getDesignFrame(), db.getName(), db.getUserName());
1270: // This dialog is modal, so when we return the values have been
1271: // filled.
1272: db.setUserName(dialog.getUserName());
1273: db.setPassword(dialog.getPassword());
1274: } else {
1275: System.out.print(I18N.get("Report.user_name") + " ["
1276: + db.getUserName() + "]: ");
1277: System.out.flush();
1278: try {
1279: BufferedReader in = new BufferedReader(
1280: new InputStreamReader(System.in));
1281: String username = in.readLine();
1282: if (username.length() > 0)
1283: db.setUserName(username);
1284: } catch (IOException e) {
1285: ErrorHandler.error(I18N.get("Report.user_name_err"));
1286: }
1287:
1288: System.out.print(I18N.get("Report.password") + ' '
1289: + db.getUserName() + ": ");
1290: System.out.flush();
1291: try {
1292: BufferedReader in = new BufferedReader(
1293: new InputStreamReader(System.in));
1294: databasePassword = in.readLine();
1295: db.setPassword(databasePassword);
1296: } catch (IOException e) {
1297: ErrorHandler.error(I18N.get("Report.password_err"));
1298: }
1299: }
1300: }
1301:
1302: /**
1303: * Returns the value of the specified parameter. First time at the start
1304: * of each report run, asks user for parameter values.
1305: *
1306: * @param paramId a parameter id
1307: */
1308: public Object getParameterValue(Object paramId) {
1309: if (!askedForParameters)
1310: askForParameters();
1311:
1312: return findParameter(paramId).getValue();
1313: }
1314:
1315: /**
1316: * Lets the caller tell the report to ask for parameters (<var>val</var> is
1317: * <code>false</code>, the default value) or to not ask (<var>val</var> is
1318: * <code>true</code>). Call this method with <var>val</var> <code>=
1319: * true</code> when you set the parameters in your Java code manually.
1320: */
1321: public void parametersSetManually(boolean val) {
1322: paramsSetManually = val;
1323: }
1324:
1325: /**
1326: * Asks the user for parameter values. If the user cancels, we throw a
1327: * <code>UserCancellationException</code>.
1328: */
1329: protected void askForParameters() throws UserCancellationException {
1330: List usedParameters = null;
1331: if (askedForParameters || paramsSetManually
1332: || (usedParameters = collectUsedParameters()).isEmpty()) {
1333: askedForParameters = true;
1334: return;
1335: }
1336:
1337: if (ErrorHandler.usingGUI()) {
1338: if (parametersHaveValues) {
1339: // Ask user about re-using the previous values
1340: String msg = I18N.get("Report.use_prev_param_vals");
1341: if (JOptionPane.showConfirmDialog(getDesignFrame(),
1342: msg, I18N.get("Report.use_prev_title"),
1343: JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
1344: askedForParameters = true;
1345: parametersHaveValues = true;
1346: return;
1347: }
1348: }
1349:
1350: // This dialog is modal. By the time it returns, the parameters
1351: // have their new values.
1352:
1353: if (new ParamAskWin(getDesignFrame(), usedParameters)
1354: .userCancelled())
1355: throw new UserCancellationException(I18N
1356: .get("Report.user_cancelled"));
1357: } else {
1358: if (paramReader == null) {
1359: ErrorHandler.error(I18N
1360: .get("Report.missing_param_xml_file"));
1361: throw new UserCancellationException(I18N
1362: .get("Report.missing_param_xml_file_short"));
1363: }
1364: try {
1365: paramReader.read();
1366: paramReader = null;
1367: } catch (Exception ex) {
1368: ErrorHandler.error(I18N.get("Report.param_file_err_1")
1369: + ' ' + paramReader.getInputName()
1370: + I18N.get("Report.param_file_err_2"), ex);
1371: throw new UserCancellationException(I18N
1372: .get("Report.param_file_err_short"));
1373: }
1374: }
1375:
1376: askedForParameters = true;
1377: parametersHaveValues = true;
1378: }
1379:
1380: /**
1381: * Asks the user for a data source file if necessary. If the user cancels,
1382: * we throw a <code>UserCancellationException</code>.
1383: */
1384: protected void askForDataSourceFile()
1385: throws UserCancellationException, FileNotFoundException {
1386: if (!dataSource.usesSourceFile() || !ErrorHandler.usingGUI())
1387: return;
1388:
1389: if (!dataSource.needsSourceFile()) {
1390: if (dataSource.alreadyUsedSourceFile()) {
1391: // Ask user about re-using the previous values
1392: String msg = I18N
1393: .get("Report.use_prev_data_source_file");
1394: if (JOptionPane.showConfirmDialog(getDesignFrame(),
1395: msg,
1396: I18N.get("Report.use_prev_data_source_title"),
1397: JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
1398: dataSource.reuseSourceFile();
1399: }
1400: }
1401: return;
1402: }
1403:
1404: Frame frame = Designer.findWindowFor(this ) == null ? null
1405: : Designer.findWindowFor(this ).getFrame();
1406: JFileChooser chooser = Designer.getChooser();
1407: Designer.setPrefsDir(chooser, "dataSourceDir");
1408: int returnVal = chooser.showOpenDialog(frame);
1409: if (returnVal == JFileChooser.APPROVE_OPTION) {
1410: Designer.savePrefsDir(chooser, "dataSourceDir");
1411: dataSource.setSourceFile(chooser.getSelectedFile()
1412: .getPath());
1413: } else
1414: throw new UserCancellationException(I18N
1415: .get("Report.user_cancelled"));
1416: }
1417:
1418: /**
1419: * Spawns a new thread that runs the report, using the layout engine to
1420: * generate output. The new thread runs the method {@link #runReport}.
1421: * <p>
1422: * You may ask why this method is called <code>run</code> and it runs another
1423: * method called <code>runReport</code> when the normal convention is for a
1424: * thread to run a {@link Runnable} object that has a <code>run</code> method.
1425: * The reason is purely historical. This method started non-threaded, and I
1426: * don't want anyone else who relies on this method to have to change their
1427: * code.
1428: *
1429: * @see #runReport
1430: */
1431: public void run() {
1432: new Thread(new Runnable() {
1433: public void run() {
1434: runReport();
1435: }
1436: }).start();
1437: }
1438:
1439: /**
1440: * Runs the data source's query and hands rows to the layout engine. This
1441: * method is called from {@link #run}, which spawns a new thread in which this
1442: * method is run. If you want to run a report, decide if you want it to run in
1443: * a separate thread or not. If so, call <code>run</code>. If not, use this
1444: * method.
1445: */
1446: public void runReport() {
1447: // Parameters
1448: askedForParameters = false; // Ask user again
1449: try {
1450: askForDataSourceFile();
1451: askForParameters();
1452: } catch (UserCancellationException e) { // Ignore
1453: return;
1454: } catch (IOException ioe) { // Handled when parsed as XML
1455: return;
1456: }
1457:
1458: // Pre-report initialization
1459: for (Iterator iter = groups.iterator(); iter.hasNext();)
1460: ((Group) iter.next()).reset();
1461: collectAggregateFields();
1462: if (startFormula != null)
1463: startFormula.eval();
1464: for (Iterator iter = formulas(); iter.hasNext();)
1465: ((Formula) iter.next()).useCache();
1466: resetCachedValues();
1467:
1468: rset = null;
1469: StatusDialog statusDialog = null;
1470:
1471: try {
1472: if (ErrorHandler.usingGUI()) {
1473: statusDialog = new StatusDialog(getDesignFrame(), I18N
1474: .get("Report.status_title"), true, I18N
1475: .get("Report.status_running"));
1476: }
1477:
1478: if (!layoutEngine.wantsMoreData())
1479: return;
1480:
1481: rset = dataSource.execute();
1482:
1483: boolean layoutStarted = false;
1484: while (layoutEngine.wantsMoreData() && rset.next()) {
1485: if (statusDialog != null) {
1486: if (statusDialog.isCancelled())
1487: throw new UserCancellationException();
1488: statusDialog.update(I18N
1489: .get("Report.processing_row")
1490: + ' ' + rowNumber());
1491: }
1492:
1493: if (!layoutStarted) {
1494: layoutEngine.start();
1495: layoutStarted = true;
1496: }
1497: processResultRow();
1498: }
1499:
1500: rset.last(); // Recall last row so we can access the data
1501: if (!layoutStarted) { // No rows in report
1502: layoutEngine.start();
1503: layoutEngine.end();
1504: } else { // Output group footers and end of report
1505: layoutEngine.groupFooters(true);
1506: layoutEngine.end();
1507: }
1508: } catch (UserCancellationException uce) {
1509: layoutEngine.cancel();
1510: } catch (SQLException sqle) {
1511: layoutEngine.cancel();
1512: ErrorHandler.error(dataSource.getQuery().toString(), sqle);
1513: } catch (Exception e) {
1514: layoutEngine.cancel();
1515: ErrorHandler.error(e);
1516: } finally {
1517: if (rset != null)
1518: rset.close();
1519:
1520: aggregateFields = null;
1521: for (Iterator iter = groups.iterator(); iter.hasNext();)
1522: ((Group) iter.next()).reset();
1523: resetCachedValues();
1524:
1525: if (statusDialog != null)
1526: statusDialog.dispose();
1527: }
1528: }
1529:
1530: /**
1531: * Returns the <code>Frame</code> associated with the design window for
1532: * this report; may be <code>null</code>.
1533: *
1534: * @return a <code>Frame</code>; may be <code>null</code>
1535: */
1536: protected Frame getDesignFrame() {
1537: Designer d = Designer.findWindowFor(this );
1538: return d == null ? null : d.getFrame();
1539: }
1540:
1541: /**
1542: * Processes a single data source row. Note that the <code>next</code>
1543: * method of the result set has already been called.
1544: */
1545: protected void processResultRow() throws java.sql.SQLException {
1546: resetCachedValues();
1547: updateGroups();
1548:
1549: // To output footers, bring back the previous row of data
1550: if (!rset.isFirst()) {
1551: rset.previous();
1552: layoutEngine.groupFooters(false);
1553: rset.next();
1554: resetCachedValues();
1555: }
1556:
1557: updateGroupCounters();
1558: updateAggregates();
1559:
1560: boolean isLastRow = rset.isLast();
1561: layoutEngine.groupHeaders(isLastRow);
1562: layoutEngine.detail(isLastRow);
1563: }
1564:
1565: /**
1566: * Tells each formula that it should re-evaluate.
1567: */
1568: protected void resetCachedValues() {
1569: for (Iterator iter = formulas(); iter.hasNext();)
1570: ((Formula) iter.next()).shouldEvaluate();
1571: for (Iterator iter = subreports(); iter.hasNext();)
1572: ((Subreport) iter.next()).clearCache();
1573: }
1574:
1575: /**
1576: * Evalues the formulas in the specified section. This is called by the
1577: * layout engine just before the section gets output.
1578: *
1579: * @param s a section
1580: */
1581: public void evaluateFormulasIn(Section s) {
1582: for (Iterator iter = s.fields(); iter.hasNext();) {
1583: Field f = (Field) iter.next();
1584: if (f instanceof FormulaField)
1585: ((FormulaField) f).getValue(); // Force evaluation
1586: }
1587: }
1588:
1589: /**
1590: * Returns the current value of the specified selectable. Only defined
1591: * when running a report.
1592: *
1593: * @return the string or Double value of the column
1594: */
1595: public Object columnValue(Selectable selectable) {
1596: // Ask data source for field number, then get value of that column
1597: return rset
1598: .getObject(dataSource.indexOfSelectable(selectable) + 1);
1599: }
1600:
1601: /**
1602: * Returns the current page number. Asks the layout engine. Only defined
1603: * when running a report.
1604: *
1605: * @return a page number
1606: */
1607: public int pageNumber() {
1608: return layoutEngine.pageNumber();
1609: }
1610:
1611: /**
1612: * Returns the current data row number. Only defined when running a report.
1613: */
1614: public int rowNumber() {
1615: return rset.getRow();
1616: }
1617:
1618: /**
1619: * Returns the current row of data. Only defined when running a report.
1620: */
1621: public DataCursor getCurrentRow() {
1622: return rset;
1623: }
1624:
1625: /**
1626: * Iterates over all sections in the report, passing the section to the
1627: * specified <code>SectionWalker</code>. The sections are visited in the
1628: * order in which they will be displayed.
1629: *
1630: * @param s a section walker
1631: */
1632: public void withSectionsDo(SectionWalker s) {
1633: reportHeaders.withSectionsDo(s);
1634: pageHeaders.withSectionsDo(s);
1635: for (Iterator iter = groups.iterator(); iter.hasNext();)
1636: ((Group) iter.next()).headers().withSectionsDo(s);
1637: details.withSectionsDo(s);
1638: for (Iterator iter = groups.iterator(); iter.hasNext();)
1639: ((Group) iter.next()).footers().withSectionsDo(s);
1640: reportFooters.withSectionsDo(s);
1641: pageFooters.withSectionsDo(s);
1642: }
1643:
1644: /**
1645: * Iterates over all fields in the report, passing the section to the
1646: * specified <code>FieldWalker</code>.
1647: *
1648: * @param f a field walker
1649: */
1650: public void withFieldsDo(final FieldWalker f) {
1651: withSectionsDo(new SectionWalker() {
1652: public void step(Section s) {
1653: for (Iterator it = s.fields(); it.hasNext();)
1654: f.step((Field) it.next());
1655: }
1656: });
1657: }
1658:
1659: /**
1660: * Collects all aggregate fields and lets each one initialize itself.
1661: * Used once at the beginning of each run.
1662: */
1663: protected void collectAggregateFields() {
1664: aggregateFields = new ArrayList();
1665: withFieldsDo(new FieldWalker() {
1666: public void step(Field f) {
1667: if (f instanceof AggregateField) {
1668: ((AggregateField) f).initialize();
1669: aggregateFields.add(f);
1670: }
1671: }
1672: });
1673: }
1674:
1675: /**
1676: * Collects all aggregate fields that are aggregating the specified field.
1677: * Used by the report design GUI.
1678: *
1679: * @param field a field
1680: * @return a list of aggregate fields
1681: */
1682: public AbstractList getAggregateFieldsFor(final Field field) {
1683: final ArrayList subs = new ArrayList();
1684: withFieldsDo(new FieldWalker() {
1685: public void step(Field f) {
1686: if (f instanceof AggregateField
1687: && ((AggregateField) f).getField() == field) {
1688: subs.add(f);
1689: }
1690: }
1691: });
1692: return subs;
1693: }
1694:
1695: /**
1696: * Updates each aggregate field.
1697: */
1698: protected void updateAggregates() {
1699: for (Iterator iter = aggregateFields.iterator(); iter.hasNext();)
1700: ((AggregateField) iter.next()).updateAggregate();
1701: }
1702:
1703: /**
1704: * Updates each group's value based on the current value of the column
1705: * each group uses.
1706: */
1707: protected void updateGroups() {
1708: for (Iterator iter = groups.iterator(); iter.hasNext();) {
1709: Group g = (Group) iter.next();
1710: g.setValue(this );
1711: }
1712: }
1713:
1714: /**
1715: * Lets each group update its line counter.
1716: */
1717: protected void updateGroupCounters() {
1718: for (Iterator iter = groups.iterator(); iter.hasNext();) {
1719: Group g = (Group) iter.next();
1720: g.updateCounter();
1721: }
1722: }
1723:
1724: /**
1725: * Remembers name of parameter XML file.
1726: *
1727: * @param f an XML file
1728: */
1729: public void setParameterXMLInput(File f) {
1730: paramReader = new ParameterReader(this , f);
1731: }
1732:
1733: /**
1734: * Remembers an input source and reads parameter values from it later. To
1735: * speicfy a URL, use InputSource("http://...")</code>.
1736: *
1737: * @param in the input source
1738: */
1739: public void setParameterXMLInput(InputSource in) {
1740: paramReader = new ParameterReader(this , in);
1741: }
1742:
1743: /**
1744: * Reads an XML file and builds the contents of this report. Uses a
1745: * <code>ReportReader</code>.
1746: *
1747: * @param f a report XML file
1748: */
1749: public void read(File f) throws Exception {
1750: new ReportReader(this ).read(f);
1751: }
1752:
1753: /**
1754: * Reads an XML stream using a <code>org.xml.sax.InputSource</code> and
1755: * builds the contents of this report. Uses a <code>ReportReader</code>.
1756: * To specify a URL, use <code>new InputSource("http://...")</code>.
1757: *
1758: * @param in a reader
1759: * @see ReportReader#read(org.xml.sax.InputSource)
1760: */
1761: public void read(org.xml.sax.InputSource in) throws Exception {
1762: new ReportReader(this ).read(in);
1763: }
1764:
1765: /**
1766: * Writes the contents of this report as an XML file.
1767: *
1768: * @param fileName a file name string
1769: */
1770: public void writeFile(String fileName) {
1771: XMLWriter out = null;
1772: try {
1773: out = new XMLWriter(new OutputStreamWriter(
1774: new FileOutputStream(fileName), XML_JAVA_ENCODING));
1775: writeXML(out);
1776: out.close();
1777: } catch (IOException ioe) {
1778: ErrorHandler
1779: .error(I18N.get("Report.write_err") + ' '
1780: + fileName, ioe, I18N
1781: .get("Report.write_err_title"));
1782: } finally {
1783: if (out != null)
1784: out.close();
1785: }
1786: }
1787:
1788: /**
1789: * Writes the contents of this report as an XML file.
1790: *
1791: * @param out an indent writer
1792: */
1793: public void writeXML(XMLWriter out) {
1794: writeXMLDecl(out);
1795: writeComment(out);
1796: writeReport(out);
1797: }
1798:
1799: protected void writeXMLDecl(XMLWriter out) {
1800: out.xmlDecl(XML_ENCODING_ATTRIBUTE);
1801: }
1802:
1803: protected void writeComment(XMLWriter out) {
1804: out.comment("Generated by DataVision version " + info.Version);
1805: out.comment(info.URL);
1806: }
1807:
1808: protected void writeReport(XMLWriter out) {
1809: out.startElement("report");
1810: out.attr("dtd-version", OUTPUT_DTD_VERSION);
1811: out.attr("name", name);
1812: out.attr("title", title);
1813: out.attr("author", author);
1814:
1815: writeDescription(out);
1816: scripting.writeXML(out);
1817: writeStartFormula(out);
1818: paperFormat.writeXML(out);
1819: defaultField.writeXML(out);
1820: ListWriter.writeList(out, usercols.values(), "usercols");
1821: dataSource.writeXML(out);
1822: ListWriter.writeList(out, subreports.values(), "subreports");
1823: ListWriter.writeList(out, parameters.values(), "parameters");
1824: ListWriter.writeList(out, formulas.values(), "formulas");
1825: ListWriter.writeList(out, reportHeaders.sections(), "headers");
1826: ListWriter.writeList(out, reportFooters.sections(), "footers");
1827: writePage(out);
1828: ListWriter.writeList(out, groups, "groups");
1829: ListWriter.writeList(out, details.sections(), "details");
1830:
1831: out.endElement();
1832: }
1833:
1834: protected void writeDescription(XMLWriter out) {
1835: out.cdataElement("description", description);
1836: }
1837:
1838: protected void writeStartFormula(XMLWriter out) {
1839: if (startFormula != null)
1840: startFormula.writeXML(out);
1841: }
1842:
1843: protected void writePage(XMLWriter out) {
1844: if (pageHeaders.isEmpty() && pageFooters.isEmpty())
1845: return;
1846:
1847: out.startElement("page");
1848: ListWriter.writeList(out, pageHeaders.sections(), "headers");
1849: ListWriter.writeList(out, pageFooters.sections(), "footers");
1850: out.endElement();
1851: }
1852:
1853: }
|