Report.java :  » Report » datavision-1.1.0 » jimm » datavision » Java Open Source

Java Open Source » Report » datavision 1.1.0 
datavision 1.1.0 » jimm » datavision » Report.java
package jimm.datavision;
import jimm.datavision.field.*;
import jimm.datavision.layout.LayoutEngine;
import jimm.datavision.source.*;
import jimm.datavision.source.sql.Database;
import jimm.datavision.gui.sql.DbPasswordDialog;
import jimm.datavision.gui.Designer;
import jimm.datavision.gui.StatusDialog;
import jimm.datavision.gui.parameter.ParamAskWin;
import jimm.util.I18N;
import jimm.util.XMLWriter;
import java.awt.Frame;
import java.io.*;
import java.util.*;
import java.sql.*;
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import org.xml.sax.*;
import org.apache.bsf.BSFException;

/**
 * A report holds data source information, accepts parameters from the user,
 * runs a query, and uses a layout engine to format the output. It may
 * contain many different sections, each of which can contain logic for
 * surpressing itself.
 *
 * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
 */
public class Report implements Nameable, Writeable {

/**
 * The string to use when reading and writing XML files. Pass it to
 * OutputStreamWriters and InputStreamReaders.
 */
public static final String XML_JAVA_ENCODING = "UTF8";
/**
 * The string to write at the top of an XML file in the XML decl.
 */
public static final String XML_ENCODING_ATTRIBUTE = "UTF-8";

protected static final double OUTPUT_DTD_VERSION = 1.2;

protected String name;
protected String title;
protected String author;
protected String description;
protected Formula startFormula;
protected DataSource dataSource;
protected HashMap formulas;
protected TreeMap parameters;
protected HashMap usercols;
protected HashMap subreports;
protected ArrayList groups;
protected SectionArea reportHeaders;
protected SectionArea reportFooters;
protected SectionArea pageHeaders;
protected SectionArea pageFooters;
protected SectionArea details;
protected DataCursor rset;
protected LayoutEngine layoutEngine;
protected PaperFormat paperFormat;
protected Collection aggregateFields;
protected String databasePassword;
protected boolean askedForParameters;
protected boolean parametersHaveValues;
protected boolean paramsSetManually;
protected ParameterReader paramReader;
/** Flag for Database data sources. */
protected boolean caseSensitiveDatabaseNames;
protected Scripting scripting;
/**
 * This field holds default format, border, and bounds values for all fields.
 * For all format ivars, if the value of the ivar is null then the value is
 * obtained from this default field's format.
 */
protected Field defaultField;

/**
 * Constructs an empty report.
 */
public Report() {
    formulas = new HashMap();
    parameters = new TreeMap();
    usercols = new HashMap();
    subreports = new HashMap();
    groups = new ArrayList();
    name = I18N.get("Report.default_name");
    title = I18N.get("Report.default_title");
    askedForParameters = false;
    parametersHaveValues = false;
    paramsSetManually = false;
    caseSensitiveDatabaseNames = true;
    paperFormat = PaperFormat.getDefault();
    scripting = new Scripting(this);

    defaultField = Field.create(new Long(0), this, null, "text",
        I18N.get("Report.default_field_name"), true);
    defaultField.setFormat(Format.createDefaultFormat());
    defaultField.setBorder(new Border(defaultField));

    initializeSections();
}

public void initializeSections() {
    reportHeaders = new SectionArea(SectionArea.REPORT_HEADER);
    reportFooters = new SectionArea(SectionArea.REPORT_FOOTER);
    pageHeaders = new SectionArea(SectionArea.PAGE_HEADER);
    pageFooters = new SectionArea(SectionArea.PAGE_FOOTER);
    details = new SectionArea(SectionArea.DETAIL);

    reportHeaders.add(new Section(this));
    pageHeaders.add(new Section(this));
    pageFooters.add(new Section(this));
    reportFooters.add(new Section(this));
    details.add(new Section(this));
}

/**
 * Sets the layout engine to use.
 *
 * @param layoutEngine a layout engine
 */
public void setLayoutEngine(LayoutEngine layoutEngine) {
    this.layoutEngine = layoutEngine;
    this.layoutEngine.setReport(this);
}

/**
 * Generates and returns a new unique id number. The number is one larger
 * than the largest in a given list of {@link Identity} objects whose
 * identifiers must be <code>Long</code> objects.
 *
 * @param iter an iterator over a collection if <code>Identity</code>
 * objects whose identifiers must be <code>Long</code>s
 * @return a <code>Long</code>
 */
protected Long generateNewId(Iterator iter) {
    long max = 0;
    while (iter.hasNext()) {
  Object id = ((Identity)iter.next()).getId();
  long longVal = ((Long)id).longValue();
  if (longVal > max)
      max = longVal;
    }
    return new Long(max + 1);
}

/**
 * Generates and returns a new unique formula id number.
 *
 * @return a long id
 */
public Long generateNewFormulaId() {
    return generateNewId(formulas.values().iterator());
}

/**
 * Generates and returns a new unique parameter id number.
 *
 * @return a long id
 */
public Long generateNewParameterId() {
    return generateNewId(parameters.values().iterator());
}

/**
 * Generates and returns a new unique user column id number.
 *
 * @return a long id
 */
public Long generateNewUserColumnId() {
    return generateNewId(usercols.values().iterator());
}

/**
 * Generates and returns a new unique user column id number.
 *
 * @return a long id
 */
public Long generateNewSubreportId() {
    return generateNewId(subreports.values().iterator());
}

/**
 * Given an id, returns the field that has that id. If no field with the
 * specified id exists, returns <code>null</code>.
 *
 * @return a field, or <code>null</code> if no field with the specified
 * id exists
 */
public Field findField(final Object id) {
    final Field listOfOne[] = new Field[1];
    listOfOne[0] = null;
    withSectionsDo(new SectionWalker() {
  public void step(Section s) {
      Field f = s.findField(id);
      if (f != null) listOfOne[0] = f;
  }
  });
    return listOfOne[0];
}

public String getName() { return name; }
public void setName(String newName) { name = newName; }

public String getTitle() { return title; }
public void setTitle(String newTitle) { title = newTitle; }

public String getAuthor() { return author; }
public void setAuthor(String newAuthor) { author = newAuthor; }

public String getDescription() { return description; }
public void setDescription(String newDescription) {
    description = newDescription;
}

/**
 * Returns the report's start formula; may be <code>null</code>.
 *
 * @return the report's start formula; may be <code>null</code>
 */
public Formula getStartFormula() { return startFormula; }
public void setStartFormula(Formula newStartFormula) {
    startFormula = newStartFormula;
}

public Scripting getScripting() { return scripting; }

/**
 * Evaluates an <var>evalString</var> using <var>language</var> and returns
 * the results. Called by {@link Formula#evaluate} after it has created the
 * <var>evalString</var>.
 *
 * @param language the language to use
 * @param evalString the string to evaluate
 * @param displayName a name (for example, a formula name) to display with
 * error messages
 * @return the result
 */
public Object eval(String language, String evalString, String displayName)
    throws BSFException
{
    return scripting.eval(language, evalString, displayName);
}

/**
 * Returns the value of the object ({@link Column}, {@link Formula},
 * {@link Parameter}, {@link UserColumn}, or {@link SpecialField})
 * identified by <var>labelOrId</var>.
 *
 * @param labelOrId the label or id of a 
 */
public Object value(String labelOrId) {
  if (labelOrId == null)
    return null;

  labelOrId = labelOrId.trim();
  if (labelOrId.length() == 0)
    return null;

  if (labelOrId.charAt(0) == '{') {
    if (labelOrId.length() == 1)
      return null;
    int endPos = labelOrId.indexOf('}');
    switch (labelOrId.charAt(1)) {
    case '@':
      Formula f = findFormulaByName(labelOrId.substring(2, endPos));
      if (f == null) return null;
      return f.evaluate(null);  // TODO: can we pass in field?
    case '?':
      Parameter p = findParameterByName(labelOrId.substring(2, endPos));
      if (p == null) return null;
      return p.getValue();
    case '%':
      // TODO: first arg is field; can we pass it in?
      return SpecialField.value(null, labelOrId.substring(2, endPos), this);
    case '!':
      UserColumn uc = findUserColumnByName(labelOrId.substring(2, endPos));
      if (uc == null) return null;
      return uc.getValue(this);
    }
  }

  if (labelOrId.startsWith("{") && labelOrId.endsWith("}"))
    labelOrId = labelOrId.substring(1, labelOrId.length() - 1);
  Column col = findColumn(labelOrId);
  if (col == null)
    return null;
  else
    return columnValue(col);
}

/**
 * Returns the field that holds default format, border, and bounds values for
 * all fields. For all format ivars, if the value of the ivar is null then the
 * value is obtained from this default field's format. If you need those
 * values, clone them before using them.
 *
 * @return the default field
 */
public Field getDefaultField() { return defaultField; }

public DataSource getDataSource() { return dataSource; }

public boolean hasDataSource() {
    return dataSource != null;
}

/**
 * Sets the data source. Called by {@link ReportReader#database}, {@link
 * ReportReader#charSepSource}, or user code.
 * <p>
 * If we already have a data source (for example, someone has called {@link
 * #setDatabaseConnection}), the existing data source will be stomped on. Both
 * {@link ReportReader#database} and {@link ReportReader#charSepSource} call
 * {@link #hasDataSource} to check first.
 *
 * @param newDataSource a new data source
 */
public void setDataSource(DataSource newDataSource) {
    dataSource = newDataSource;
}

/**
 * Sets the database connection. If this is called before reading a
 * report XML file, then this connection will be used instead of
 * the connection information specified in the report.
 * <p>
 * <em>Note:</em> this connection will <em>not</em> be closed when
 * the report finishes or even if the database object's connection
 * is reset.
 *
 * @param conn a database connection
 */
public void setDatabaseConnection(Connection conn) throws SQLException {
    dataSource = new Database(conn, this);
    databasePassword = "";  // So user won't be asked for password
}

/**
 * Returns the value of the <var>caseSensitiveDatabaseNames</var> flag.
 * By default, this flag is <code>true</code>.
 *
 * @return <code>true</code> if all mixed-case names should be quoted when
 * appropriate
 */
public boolean caseSensitiveDatabaseNames() {
    return caseSensitiveDatabaseNames;
}

/**
 * Sets the value of <var>caseSensitiveDatabaseNames</var>.
 * Normally, any database data sources' query objects quote all mixed-case
 * names where appropriate.
 */
public void setCaseSensitiveDatabaseNames(boolean val) {
    caseSensitiveDatabaseNames = val;
}

/**
 * Tells this report to reload all references to column objects. Called
 * by a database when it resets its connection.
 *
 * @see Database#reset
 */
public void reloadColumns() {
    for (Iterator iter = groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  g.reloadSelectable(dataSource);
    }
    withFieldsDo(new FieldWalker() {
  public void step(Field f) {
      if (f instanceof ColumnField) {
    ColumnField cf = (ColumnField)f;
    cf.setColumn(dataSource.findColumn(cf.getColumn().getId()));
      }
  }
  });

    // Let the data source tell its ancillary objects (such as the query)
    // to reload its columns.
    dataSource.reloadColumns();
}

/**
 * Given an id (a column name), returns the column that has that id. If no
 * column with the specified id exists, returns <code>null</code>. Calls
 * {@link DataSource#findColumn}.
 *
 * @return a column, or <code>null</code> if no column with the specified
 * id exists
 */
public Column findColumn(String id) { return dataSource.findColumn(id); }

public PaperFormat getPaperFormat() { return paperFormat; }
public void setPaperFormat(PaperFormat newPaperFormat) {
    paperFormat = newPaperFormat;
}

/**
 * Figure out what <var>obj</var> is and add it.
 */
public void add(Object obj) {
    if (obj instanceof Parameter) addParameter((Parameter)obj);
    else if (obj instanceof Formula) addFormula((Formula)obj);
    else if (obj instanceof UserColumn) addUserColumn((UserColumn)obj);
    else if (obj instanceof Group) addGroup((Group)obj);
    else      // Shouldn't happen
  ErrorHandler.error(I18N.get("Report.add_err_1") + ' '
         + obj.getClass().getName() + ' '
         + I18N.get("Report.add_err_2"));
}

/**
 * Figure out what <var>obj</var> is and remove it.
 */
public void remove(Object obj) {
    if (obj instanceof Field)
  removeField((Field)obj);
    else if (obj instanceof Formula)
  removeFormula((Formula)obj);
    else if (obj instanceof Parameter)
  removeParameter((Parameter)obj);
    else if (obj instanceof UserColumn)
  removeUserColumn((UserColumn)obj);
    else if (obj instanceof Group)
  removeGroup((Group)obj);
    else if (obj instanceof Section)
  removeSection((Section)obj);
    else
  ErrorHandler.error(I18N.get("Report.remove_err_1") + ' '
         + obj.getClass().getName()
         + I18N.get("Report.remove_err_2"));
}

// ---------------- parameters

/**
 * Returns the parameter with the specified id or <code>null</code> if one is
 * not found. <em>Note</em>: don't use this method if you need a parameter's
 * value. That value is supplied by the user. Call {@link
 * Report#getParameterValue} instead, which asks the user to supply the value.
 *
 * @param id the parameter id
 * @return the parameter with that id or <code>null</code> if one is not found
 */
public Parameter findParameter(Object id) {
    if (id instanceof String)
  id = new Long((String)id);
    return (Parameter)parameters.get(id);
}

/**
 * Returns the parameter with the specified name or <code>null</code>
 * if one is not found.
 *
 * @param name the name string
 * @return the parameter with that name or <code>null</code> if one is not
 * found
 */
public Parameter findParameterByName(String name) {
    if (name == null || name.length() == 0)
  return null;

    name = name.toLowerCase();
    for (Iterator iter = parameters.values().iterator(); iter.hasNext(); ) {
  Parameter p = (Parameter)iter.next();
  if (name.equals(p.getName().toLowerCase()))
      return p;
    }
    return null;
}

public void addParameter(Parameter p) {
    parameters.put(p.getId(), p);
}
public void removeParameter(Parameter p) {
    parameters.remove(p.getId());
}
public Iterator parameters() { return parameters.values().iterator(); }

// ---------------- formulas

/**
 * Returns the formula with the specified id or <code>null</code>
 * if one is not found.
 *
 * @param id the formula id
 * @return the formula with that id or <code>null</code> if one is not found
 */
public Formula findFormula(Object id) {
    if (id instanceof String)
  id = new Long((String)id);
    return (Formula)formulas.get(id);
}

/**
 * Returns the formula with the specified name or <code>null</code>
 * if one is not found.
 *
 * @param name the name string
 * @return the formula with that name or <code>null</code> if one is not found
 */
public Formula findFormulaByName(String name) {
    if (name == null || name.length() == 0)
  return null;

    name = name.toLowerCase();
    for (Iterator iter = formulas.values().iterator(); iter.hasNext(); ) {
  Formula f = (Formula)iter.next();
  if (name.equals(f.getName().toLowerCase()))
      return f;
    }
    return null;
}

public void addFormula(Formula f) {
    formulas.put(f.getId(), f);
}
public void removeFormula(Formula f) {
    formulas.remove(f.getId());
}
public Iterator formulas() { return formulas.values().iterator(); }

// ---------------- user columns

/**
 * Returns the user column with the specified id or <code>null</code>
 * if one is not found.
 *
 * @param id the user column id
 * @return the user column with that id or <code>null</code> if one
 * is not found
 */
public UserColumn findUserColumn(Object id) {
    if (id instanceof String)
  id = new Long((String)id);
    return (UserColumn)usercols.get(id);
}

/**
 * Returns the user column with the specified name or <code>null</code>
 * if one is not found.
 *
 * @param name the name string
 * @return the user column with that name or <code>null</code> if one
 * is not found
 */
public UserColumn findUserColumnByName(String name) {
    if (name == null || name.length() == 0)
  return null;

    name = name.toLowerCase();
    for (Iterator iter = usercols.values().iterator(); iter.hasNext(); ) {
  UserColumn f = (UserColumn)iter.next();
  if (name.equals(f.getName().toLowerCase()))
      return f;
    }
    return null;
}

public void addUserColumn(UserColumn uc) {
    usercols.put(uc.getId(), uc);
}
public void removeUserColumn(UserColumn uc) {
    usercols.remove(uc.getId());
}
public Iterator userColumns() { return usercols.values().iterator(); }

// ---------------- subreports

/**
 * Returns the subreport with the specified id or <code>null</code>
 * if one is not found.
 *
 * @param id the subreport id
 * @return the subreport with that id or <code>null</code> if one is not found
 */
public Subreport findSubreport(Object id) {
    if (id instanceof String)
  id = new Long((String)id);
    return (Subreport)subreports.get(id);
}

public void addSubreport(Subreport sub) {
    subreports.put(sub.getId(), sub);
}
public void removeSubreport(Subreport sub) {
    subreports.remove(sub.getId());
}
public Iterator subreports() { return subreports.values().iterator(); }

// ---------------- selectable methods

public Selectable findSelectable(Object id, String type) {
    if ("column".equals(type))
  return findColumn(id.toString());
    else      // "usercol"
  return findUserColumn(id);
}

// ---------------- section manipulation

/**
 * Returns the section area corresponding to <var>area</var>, but
 * <em>only</em> if <var>area</var> is not group header or group footer. Both
 * of those possibly apply to multiple groups, so we can't tell which one is
 * desired.
 *
 * @param area one of the <code>SectionArea.*</code> constants
 * @return the section area corresponding to <var>area</var>
 */
public SectionArea getSectionArea(int area) {
    switch (area) {
    case SectionArea.REPORT_HEADER: return reportHeaders;
    case SectionArea.REPORT_FOOTER: return reportFooters;
    case SectionArea.PAGE_HEADER: return pageHeaders;
    case SectionArea.PAGE_FOOTER: return pageFooters;
    case SectionArea.DETAIL: return details;
    default:
  return null;
    }
}

/**
 * Returns <code>true</code> if this report contains the specified section.
 *
 * @return <code>true</code> if this report contains the specified section
 */
public boolean contains(Section s) {
    return s.getArea() != null;
}

/**
 * Returns the first section in the list of the specified type. If the
 * type is <code>GROUP_HEADER</code> or <code>GROUP_FOOTER</code>, return
 * (a) <code>null</code> if there are no groups in the report or
 * (b) the first header or footer of the first group.
 * <p>
 * Used by {@link jimm.datavision.gui.cmd.FieldClipping} when trying to paste
 * a field into either some other report or the same report if the original
 * section is no longer in that report.
 *
 * @return a section; may be <code>null</code>
 */
public Section getFirstSectionByArea(int area) {
    switch (area) {
    case SectionArea.GROUP_HEADER:
  if (groups.isEmpty()) return null;
  return ((Group)groups.get(0)).headers().first();
    case SectionArea.GROUP_FOOTER:
  if (groups.isEmpty()) return null;
  return ((Group)groups.get(0)).footers().first();
    default:
  return getSectionArea(area).first();
    }
}

/**
 * Returns a structure useful only by this report for re-inserting a section.
 * This structure may later be passed to {@link #reinsertSection}. Used by
 * {@link jimm.datavision.gui.cmd.DeleteSectionCommand}.
 * <p>
 * We assume that <var>s</var> is contained within some {@link SectionArea}.
 *
 * @param s the section in question
 * @return section location information
 */
public ReportSectionLoc getSectionLocation(Section s) {
    SectionArea area = s.getArea();
    return new ReportSectionLoc(s, area, area.indexOf(s));
}

/**
 * Reinserts a section based on the location information previously retrieved
 * by a call to {@link #getSectionLocation}. Used by {@link
 * jimm.datavision.gui.cmd.DeleteSectionCommand}.
 *
 * @param loc a section locatction
 */
public void reinsertSection(ReportSectionLoc loc) {
    loc.area.add(loc.index, loc.section);
}

// ---------------- section areas

public SectionArea headers() { return reportHeaders; }
public SectionArea footers() { return reportFooters; }
public SectionArea pageHeaders() { return pageHeaders; }
public SectionArea pageFooters() { return pageFooters; }
public SectionArea details() { return details; }

// ---------------- groups

public void addGroup(Group g) {
    groups.add(g);
    dataSource.removeSort(g.getSelectable());
}
public void removeGroup(Group g) { groups.remove(g); }
public void removeAllGroups() { groups.clear(); }
public Iterator groups() { return groups.iterator(); }
public int countGroups() { return groups.size(); }
public boolean hasGroups() { return countGroups() > 0; }

/**
 * Returns the last (innermost) group in the report, or <code>null</code>
 * if there are no groups.
 *
 * @return the last (innermost) group in the report, or <code>null</code>
 * if there are no groups.
 */
public Group innermostGroup() {
    return groups.size() > 0 ? (Group)groups.get(groups.size() - 1) : null;
}

/**
 * Returns an iterator over the groups in reverse order. Useful when
 * displaying group footers.
 *
 * @return an iterator over the groups, in reverse order
 */
public Iterator groupsReversed() {
    ArrayList reversed = (ArrayList)groups.clone();
    Collections.reverse(reversed);
    return reversed.iterator();
}

public void removeField(Field f) {
    Section s = sectionContaining(f);
    if (s != null)
  s.removeField(f);
}

/**
 * Returns <code>true</code> if the specified field exists within this
 * report.
 *
 * @param f a field
 * @return <code>true</code> if the specified field exists within this
 * report
 */
public boolean contains(Field f) {
    return sectionContaining(f) != null;
}

/**
 * Returns the section containing the specified field, or <code>null</code>
 * if the field is not in the report.
 *
 * @param f a field
 * @return the section containing the specified field, or <code>null</code>
 * if the field is not in the report
 */
public Section sectionContaining(Field f) {
    Section s;
    Iterator iter, iter2;
    for (iter = reportHeaders.iterator(); iter.hasNext(); ) {
  s = (Section)iter.next();
  if (s.contains(f)) return s;
    }
    for (iter = pageHeaders.iterator(); iter.hasNext(); ) {
  s = (Section)iter.next();
  if (s.contains(f)) return s;
    }
    for (iter = groups.iterator(); iter.hasNext(); ) {
  for (iter2 = ((Group)iter.next()).headers().iterator();
       iter2.hasNext(); ) {
      s = (Section)iter2.next();
      if (s.contains(f)) return s;
  }
    }
    for (iter = details.iterator(); iter.hasNext(); ) {
  s = (Section)iter.next();
  if (s.contains(f)) return s;
    }
    for (iter = groups.iterator(); iter.hasNext(); ) {
  for (iter2 = ((Group)iter.next()).footers().iterator();
       iter2.hasNext(); ) {
      s = (Section)iter2.next();
      if (s.contains(f)) return s;
  }
    }
    for (iter = reportFooters.iterator(); iter.hasNext(); ) {
  s = (Section)iter.next();
  if (s.contains(f)) return s;
    }
    for (iter = pageFooters.iterator(); iter.hasNext(); ) {
  s = (Section)iter.next();
  if (s.contains(f)) return s;
    }

    return null;
}

/**
 * Returns <code>true</code> if the specified field exists within this
 * report either directly (as a field) or indirectly (as a formula used
 * by a aggregate, parameter, user column, or another formula).
 *
 * @param f a field
 * @return <code>true</code> if the specified field exists within this
 * report
 */
public boolean containsReferenceTo(final Field f) {
    if (startFormula != null && startFormula.refersTo(f))
  return true;

    final boolean[] answer = new boolean[1];
    answer[0] = false;

    withSectionsDo(new SectionWalker() {
  public void step(Section s) {
      if (s.containsReferenceTo(f))
    answer[0] = true;
  }
  });

    return answer[0];
}

/**
 * Returns <code>true</code> if the specified formula exists within this
 * report either directly (as a formula field) or indirectly (as a formula
 * used by a aggregate or by another formula). This is not the same as
 * answering the question, "Does this report contain this formula?" because
 * we want to know if the visual portion of the report or some other formula
 * accesses the specified formula.
 *
 * @param f a formula
 * @return <code>true</code> if the specified parameter exists within some
 * visual element of the report or within some formula
 */
public boolean containsReferenceTo(final Formula f) {
    if (startFormula != null && startFormula.refersTo(f))
  return true;

    final boolean[] answer = new boolean[1];
    answer[0] = false;

    withSectionsDo(new SectionWalker() {
  public void step(Section s) {
      if (s.containsReferenceTo(f))
    answer[0] = true;
  }
  });

    return answer[0];
}

/**
 * Returns <code>true</code> if the specified user column exists within
 * this report either directly (as a user column field) or indirectly
 * (inside a aggregate or formula). This is not the same as answering the
 * question, "Does this report contain this user column?" because we want
 * to know if the visual portion of the report or some formula accesses the
 * specified user column.
 *
 * @param uc a user column
 * @return <code>true</code> if the specified parameter exists within some
 * visual element of the report or within some formula
 */
public boolean containsReferenceTo(final UserColumn uc) {
    if (startFormula != null && startFormula.refersTo(uc))
  return true;

    final boolean[] answer = new boolean[1];
    answer[0] = false;

    withSectionsDo(new SectionWalker() {
  public void step(Section s) {
      if (s.containsReferenceTo(uc))
    answer[0] = true;
  }
  });

    return answer[0];
}

/**
 * Returns <code>true</code> if the specified parameter exists within this
 * report either directly (as a parameter field) or indirectly (as a
 * parameter used by a aggregate or by another parameter or in the query's
 * where clause). This is not the same as answering the question, "Does
 * this report contain this parameter?" because we want to know if the
 * visual portion of the report or some formula accesses the specified
 * parameter.
 *
 * @param p a parameter
 * @return <code>true</code> if the specified parameter exists within some
 * visual element of the report or within some formula or within the
 * query's where clause
 */
public boolean containsReferenceTo(final Parameter p) {
    if (startFormula != null && startFormula.refersTo(p))
  return true;

    if (dataSource.containsReferenceTo(p))
  return true;

    final boolean[] answer = new boolean[1];
    answer[0] = false;

    withSectionsDo(new SectionWalker() {
  public void step(Section s) {
      if (s.containsReferenceTo(p))
    answer[0] = true;
  }
  });

    return answer[0];
}

/**
 * Returns a list of all the parameters actually used in the report.
 *
 * @return a list of parameters
 */
protected List collectUsedParameters() {
    ArrayList list = new ArrayList();
    for (Iterator iter = parameters.values().iterator(); iter.hasNext(); ) {
  Parameter p = (Parameter)iter.next();
  if (containsReferenceTo(p))
      list.add(p);
    }
    return list;
}

/**
 * Returns the group associated with the specified column, or
 * <code>null</code> if there isn't one.
 *
 * @param selectable a selectable field
 * @return the group associated with this column, or <code>null</code>
 * if there isn't one
 */
public Group findGroup(Selectable selectable) {
    for (Iterator iter = groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  if (g.getSelectable() == selectable)
      return g;
    }
    return null;
}

/**
 * Returns the group associated with the specified section, or
 * <code>null</code> if there isn't one.
 *
 * @param section a section
 * @return the group associated with this section, or <code>null</code>
 * if there isn't one
 */
public Group findGroup(Section section) {
    for (Iterator iter = groups(); iter.hasNext(); ) {
  Group group = (Group)iter.next();
  if (group.contains(section))
      return group;
    }
    return null;
}

/**
 * Returns <code>true</code> if the specified section is contained in
 * any group. The section may be either a header or a footer section.
 *
 * @param section a section
 * @return <code>true</code> if the section is within some group
 */
public boolean isInsideGroup(Section section) {
    return findGroup(section) != null;
}

/**
 * Returns <code>true</code> if the specified data source column is a
 * group column.
 *
 * @return <code>true</code> if the specified data source column is a
 * group column
 */
public boolean isUsedBySomeGroup(Selectable g) {
    return findGroup(g) != null;
}

/**
 * Creates and returns a new section below the specified one.
 *
 * @param goBelowThis a section
 * @return a new section
 */
public Section insertSectionBelow(Section goBelowThis) {
    return insertSectionBelow(null, goBelowThis);
}

/**
 * Inserts a (possibly newly created) section below the specified one.
 * Returns the inserted section.
 *
 * @param section the section to insert
 * @param goBelowThis <var>section</var> goes below this one; 
 * @return a new section
 */
public Section insertSectionBelow(Section section, Section goBelowThis) {
    if (section == null)
  section = new Section(this);

    if (goBelowThis == null)
  return reportHeaders.insertAfter(section, null);

    if (reportHeaders.contains(goBelowThis))
  return reportHeaders.insertAfter(section, goBelowThis);
    else if (reportFooters.contains(goBelowThis))
  return reportFooters.insertAfter(section, goBelowThis);
    else if (pageHeaders.contains(goBelowThis))
  return pageHeaders.insertAfter(section, goBelowThis);
    else if (pageFooters.contains(goBelowThis))
  return pageFooters.insertAfter(section, goBelowThis);
    else if (details.contains(goBelowThis))
  return details.insertAfter(section, goBelowThis);

    for (Iterator iter = groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  if (g.headers().contains(goBelowThis))
      return g.headers().insertAfter(section, goBelowThis);
  else if (g.footers().contains(goBelowThis))
      return g.footers().insertAfter(section, goBelowThis);
    }

    return null;    // Should not happen
}

/**
 * Removes the specified section.
 *
 * @param s a section
 */
public void removeSection(Section s) {
    if (reportHeaders.contains(s))
  reportHeaders.remove(s);
    else if (reportFooters.contains(s))
  reportFooters.remove(s);
    else if (pageHeaders.contains(s))
  pageHeaders.remove(s);
    else if (pageFooters.contains(s))
  pageFooters.remove(s);
    else if (details.contains(s))
  details.remove(s);
    else {
  for (Iterator iter = groups(); iter.hasNext(); ) {
      Group g = (Group)iter.next();
      if (g.headers().contains(s)) {
    g.headers().remove(s);
    return;
      }
      else if (g.footers().contains(s)) {
    g.footers().remove(s);
    return;
      }
  }
    }
}

/**
 * Returns <code>true</code> if this report contains some field anywhere.
 * Useful when the GUI is enabling menu items, for example.
 *
 * @return <code>true</code> if this report contains some field anywhere
 */
public boolean hasFields() {
    final boolean[] answer = new boolean[1];
    answer[0] = false;
    withFieldsDo(new FieldWalker() {
  public void step(Field f) {
      answer[0] = true;
  }
  });
    return answer[0];
}

/**
 * Returns <code>true</code> if this report contains some parameter field
 * anywhere. This doesn't determine if any parameters have been created,
 * but rather if any of the parameters are actually used in the report.
 * <p>
 * This method is not used by DataVision, but is useful for apps embedding
 * DataVision that want to answer the question, "Do I have to read in
 * a parameter XML file?"
 *
 * @return <code>true</code> if this report contains some parameter field
 * anywhere
 */
public boolean hasParameterFields() {
    final boolean answer[] = new boolean[1];
    answer[0] = false;
    withFieldsDo(new FieldWalker() {
  public void step(Field f) {
      if (f instanceof ParameterField) answer[0] = true;
  }
  });
    return answer[0];
}

/**
 * Returns <code>true</code> if the specified section is the only section
 * of its kind; that is, the only section in the collection in which it
 * is contained.
 *
 * @param s a report section
 * @return <code>true</code> if this section is a loner
 */
public boolean isOneOfAKind(Section s) {
    if (reportHeaders.contains(s))
  return reportHeaders.size() == 1;
    if (reportFooters.contains(s))
  return reportFooters.size() == 1;
    if (pageHeaders.contains(s))
  return pageHeaders.size() == 1;
    if (pageFooters.contains(s))
  return pageFooters.size() == 1;
    if (details.contains(s))
  return details.size() == 1;

    for (Iterator iter = groups(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  if (g.headers().contains(s))
      return g.headers().size() == 1;
  if (g.footers().contains(s))
      return g.footers().size() == 1;
    }

    return false;
}

/**
 * Sets the database password.
 */
public void setDatabasePassword(String pwd) { databasePassword = pwd; }

/**
 * Sets a database's user name and password. Called from {@link
 * Database#initializeConnection}. If we already have the password, give it to
 * the database. If we don't, ask the user for both the user name (supplied to
 * us) and the password and give them to the database.
 *
 * @param db the database
 */
public void askForPassword(Database db) {
    if (databasePassword != null) {
  db.setPassword(databasePassword);
  return;
    }

    if (ErrorHandler.usingGUI()) {
  DbPasswordDialog dialog =
      new DbPasswordDialog(getDesignFrame(), db.getName(),
         db.getUserName());
  // This dialog is modal, so when we return the values have been
  // filled.
  db.setUserName(dialog.getUserName());
  db.setPassword(dialog.getPassword());
    }
    else {
  System.out.print(I18N.get("Report.user_name") + " ["
       + db.getUserName() + "]: ");
  System.out.flush();
  try {
      BufferedReader in =
    new BufferedReader(new InputStreamReader(System.in));
      String username = in.readLine();
      if (username.length() > 0)
    db.setUserName(username);
  }
  catch (IOException e) {
      ErrorHandler.error(I18N.get("Report.user_name_err"));
  }

  System.out.print(I18N.get("Report.password") + ' ' + db.getUserName()
       + ": ");
  System.out.flush();
  try {
      BufferedReader in =
    new BufferedReader(new InputStreamReader(System.in));
      databasePassword = in.readLine();
      db.setPassword(databasePassword);
  }
  catch (IOException e) {
      ErrorHandler.error(I18N.get("Report.password_err"));
  }
    }
}

/**
 * Returns the value of the specified parameter. First time at the start
 * of each report run, asks user for parameter values.
 *
 * @param paramId a parameter id
 */
public Object getParameterValue(Object paramId) {
    if (!askedForParameters)
  askForParameters();

    return findParameter(paramId).getValue();
}

/**
 * Lets the caller tell the report to ask for parameters (<var>val</var> is
 * <code>false</code>, the default value) or to not ask (<var>val</var> is
 * <code>true</code>). Call this method with <var>val</var> <code>=
 * true</code> when you set the parameters in your Java code manually.
 */ 
public void parametersSetManually(boolean val) { paramsSetManually = val; }

/**
 * Asks the user for parameter values. If the user cancels, we throw a
 * <code>UserCancellationException</code>.
 */
protected void askForParameters() throws UserCancellationException {
    List usedParameters = null;
    if (askedForParameters || paramsSetManually
  || (usedParameters = collectUsedParameters()).isEmpty())
    {
  askedForParameters = true;
  return;
    }

    if (ErrorHandler.usingGUI()) {
  if (parametersHaveValues) {
      // Ask user about re-using the previous values
      String msg = I18N.get("Report.use_prev_param_vals");
      if (JOptionPane.showConfirmDialog(getDesignFrame(), msg,
                I18N.get("Report.use_prev_title"),
                JOptionPane.YES_NO_OPTION)
    == JOptionPane.YES_OPTION)
    {
        askedForParameters = true;
        parametersHaveValues = true;
        return;
    }
  }

  // This dialog is modal. By the time it returns, the parameters
  // have their new values.
      
  if (new ParamAskWin(getDesignFrame(), usedParameters).userCancelled())
      throw new UserCancellationException(I18N.get("Report.user_cancelled"));
    }
    else {
  if (paramReader == null) {
      ErrorHandler.error(I18N.get("Report.missing_param_xml_file"));
      throw new UserCancellationException(I18N.get("Report.missing_param_xml_file_short"));
  }
  try {
      paramReader.read();
      paramReader = null;
  }
  catch (Exception ex) {
      ErrorHandler.error(I18N.get("Report.param_file_err_1") + ' '
             + paramReader.getInputName()
             + I18N.get("Report.param_file_err_2"), ex);
      throw new UserCancellationException(I18N.get("Report.param_file_err_short"));
  }
    }

    askedForParameters = true;
    parametersHaveValues = true;
}

/**
 * Asks the user for a data source file if necessary. If the user cancels,
 * we throw a <code>UserCancellationException</code>.
 */
protected void askForDataSourceFile()
    throws UserCancellationException, FileNotFoundException
{
    if (!dataSource.usesSourceFile() || !ErrorHandler.usingGUI())
  return;

    if (!dataSource.needsSourceFile()) {
        if (dataSource.alreadyUsedSourceFile()) {
            // Ask user about re-using the previous values
            String msg = I18N.get("Report.use_prev_data_source_file");
            if (JOptionPane.showConfirmDialog(getDesignFrame(), msg,
                I18N.get("Report.use_prev_data_source_title"),
                JOptionPane.YES_NO_OPTION)
         == JOptionPane.YES_OPTION)
      {
    dataSource.reuseSourceFile();
      }
        }
        return;
    }

    Frame frame = Designer.findWindowFor(this) == null ? null
  : Designer.findWindowFor(this).getFrame();
    JFileChooser chooser = Designer.getChooser();
    Designer.setPrefsDir(chooser,"dataSourceDir");
    int returnVal = chooser.showOpenDialog(frame);
    if (returnVal == JFileChooser.APPROVE_OPTION) {
        Designer.savePrefsDir(chooser,"dataSourceDir");
  dataSource.setSourceFile(chooser.getSelectedFile().getPath());
    }
    else
  throw new UserCancellationException(I18N.get("Report.user_cancelled"));
}

/**
 * Spawns a new thread that runs the report, using the layout engine to
 * generate output. The new thread runs the method {@link #runReport}.
 * <p>
 * You may ask why this method is called <code>run</code> and it runs another
 * method called <code>runReport</code> when the normal convention is for a
 * thread to run a {@link Runnable} object that has a <code>run</code> method.
 * The reason is purely historical. This method started non-threaded, and I
 * don't want anyone else who relies on this method to have to change their
 * code.
 *
 * @see #runReport
 */
public void run() {
    new Thread(new Runnable() {
  public void run() { runReport(); }
  }).start();
}

/**
 * Runs the data source's query and hands rows to the layout engine. This
 * method is called from {@link #run}, which spawns a new thread in which this
 * method is run. If you want to run a report, decide if you want it to run in
 * a separate thread or not. If so, call <code>run</code>. If not, use this
 * method.
 */
public void runReport() {
    // Parameters
    askedForParameters = false;  // Ask user again
    try {
  askForDataSourceFile();
  askForParameters();
    }
    catch (UserCancellationException e) { // Ignore
  return;
    }
    catch (IOException ioe) {  // Handled when parsed as XML
  return;
    }

    // Pre-report initialization
    for (Iterator iter = groups.iterator(); iter.hasNext(); )
  ((Group)iter.next()).reset();
    collectAggregateFields();
    if (startFormula != null)
  startFormula.eval();
    for (Iterator iter = formulas(); iter.hasNext(); )
  ((Formula)iter.next()).useCache();
    resetCachedValues();

    rset = null;
    StatusDialog statusDialog = null;

    try {
  if (ErrorHandler.usingGUI()) {
      statusDialog = new StatusDialog(getDesignFrame(),
              I18N.get("Report.status_title"),
              true,
              I18N.get("Report.status_running"));
  }

  if (!layoutEngine.wantsMoreData())
      return;

  rset = dataSource.execute();

  boolean layoutStarted = false;
  while (layoutEngine.wantsMoreData() && rset.next()) {
      if (statusDialog != null) {
    if (statusDialog.isCancelled())
        throw new UserCancellationException();
    statusDialog.update(I18N.get("Report.processing_row") + ' '
            + rowNumber());
      }

      if (!layoutStarted) {
    layoutEngine.start();
    layoutStarted = true;
      }
      processResultRow();
  }

  rset.last();    // Recall last row so we can access the data
  if (!layoutStarted) {  // No rows in report
      layoutEngine.start();
      layoutEngine.end();
  }
  else {      // Output group footers and end of report
      layoutEngine.groupFooters(true);
      layoutEngine.end();
  }
    }
    catch (UserCancellationException uce) {
  layoutEngine.cancel();
    }
    catch (SQLException sqle) {
  layoutEngine.cancel();
  ErrorHandler.error(dataSource.getQuery().toString(), sqle);
    }
    catch (Exception e) {
  layoutEngine.cancel();
  ErrorHandler.error(e);
    }
    finally {
  if (rset != null) rset.close();

  aggregateFields = null;
  for (Iterator iter = groups.iterator(); iter.hasNext(); )
    ((Group)iter.next()).reset();
  resetCachedValues();

  if (statusDialog != null)
      statusDialog.dispose();
    }
}

/**
 * Returns the <code>Frame</code> associated with the design window for
 * this report; may be <code>null</code>.
 *
 * @return a <code>Frame</code>; may be <code>null</code>
 */
protected Frame getDesignFrame() {
    Designer d = Designer.findWindowFor(this);
    return d == null ? null : d.getFrame();
}

/**
 * Processes a single data source row. Note that the <code>next</code>
 * method of the result set has already been called.
 */
protected void processResultRow() throws java.sql.SQLException {
    resetCachedValues();
    updateGroups();

    // To output footers, bring back the previous row of data
    if (!rset.isFirst()) {
  rset.previous();
  layoutEngine.groupFooters(false);
  rset.next();
  resetCachedValues();
    }

    updateGroupCounters();
    updateAggregates();

    boolean isLastRow = rset.isLast();
    layoutEngine.groupHeaders(isLastRow);
    layoutEngine.detail(isLastRow);
}

/**
 * Tells each formula that it should re-evaluate.
 */
protected void resetCachedValues() {
    for (Iterator iter = formulas(); iter.hasNext(); )
  ((Formula)iter.next()).shouldEvaluate();
    for (Iterator iter = subreports(); iter.hasNext(); )
  ((Subreport)iter.next()).clearCache();
}

/**
 * Evalues the formulas in the specified section. This is called by the
 * layout engine just before the section gets output.
 *
 * @param s a section
 */
public void evaluateFormulasIn(Section s) {
    for (Iterator iter = s.fields(); iter.hasNext(); ) {
  Field f = (Field)iter.next();
  if (f instanceof FormulaField)
      ((FormulaField)f).getValue(); // Force evaluation
    }
}

/**
 * Returns the current value of the specified selectable. Only defined
 * when running a report.
 *
 * @return the string or Double value of the column
 */
public Object columnValue(Selectable selectable) {
    // Ask data source for field number, then get value of that column
    return rset.getObject(dataSource.indexOfSelectable(selectable) + 1);
}

/**
 * Returns the current page number. Asks the layout engine. Only defined
 * when running a report.
 *
 * @return a page number
 */
public int pageNumber() {
    return layoutEngine.pageNumber();
}

/**
 * Returns the current data row number. Only defined when running a report.
 */
public int rowNumber() {
    return rset.getRow();
}

/**
 * Returns the current row of data. Only defined when running a report.
 */
public DataCursor getCurrentRow() {
    return rset;
}

/**
 * Iterates over all sections in the report, passing the section to the
 * specified <code>SectionWalker</code>. The sections are visited in the
 * order in which they will be displayed.
 *
 * @param s a section walker
 */
public void withSectionsDo(SectionWalker s) {
    reportHeaders.withSectionsDo(s);
    pageHeaders.withSectionsDo(s);
    for (Iterator iter = groups.iterator(); iter.hasNext(); )
  ((Group)iter.next()).headers().withSectionsDo(s);
    details.withSectionsDo(s);
    for (Iterator iter = groups.iterator(); iter.hasNext(); )
  ((Group)iter.next()).footers().withSectionsDo(s);
    reportFooters.withSectionsDo(s);
    pageFooters.withSectionsDo(s);
}

/**
 * Iterates over all fields in the report, passing the section to the
 * specified <code>FieldWalker</code>.
 *
 * @param f a field walker
 */
public void withFieldsDo(final FieldWalker f) {
    withSectionsDo(new SectionWalker() {
  public void step(Section s) {
      for (Iterator it = s.fields(); it.hasNext(); )
    f.step((Field)it.next());
  }
  });
}

/**
 * Collects all aggregate fields and lets each one initialize itself.
 * Used once at the beginning of each run.
 */
protected void collectAggregateFields() {
    aggregateFields = new ArrayList();
    withFieldsDo(new FieldWalker() {
  public void step(Field f) {
      if (f instanceof AggregateField) {
    ((AggregateField)f).initialize();
    aggregateFields.add(f);
      }
  }
  });
}

/**
 * Collects all aggregate fields that are aggregating the specified field.
 * Used by the report design GUI.
 * 
 * @param field a field
 * @return a list of aggregate fields
 */
public AbstractList getAggregateFieldsFor(final Field field) {
    final ArrayList subs = new ArrayList();
    withFieldsDo(new FieldWalker() {
  public void step(Field f) {
      if (f instanceof AggregateField
    && ((AggregateField)f).getField() == field)
      {
    subs.add(f);
      }
  }
  });
    return subs;
}

/**
 * Updates each aggregate field.
 */
protected void updateAggregates() {
    for (Iterator iter = aggregateFields.iterator(); iter.hasNext(); )
  ((AggregateField)iter.next()).updateAggregate();
}

/**
 * Updates each group's value based on the current value of the column
 * each group uses.
 */
protected void updateGroups() {
    for (Iterator iter = groups.iterator(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  g.setValue(this);
    }
}

/**
 * Lets each group update its line counter.
 */
protected void updateGroupCounters() {
    for (Iterator iter = groups.iterator(); iter.hasNext(); ) {
  Group g = (Group)iter.next();
  g.updateCounter();
    }
}

/**
 * Remembers name of parameter XML file.
 *
 * @param f an XML file
 */
public void setParameterXMLInput(File f) {
    paramReader = new ParameterReader(this, f);
}

/**
 * Remembers an input source and reads parameter values from it later. To
 * speicfy a URL, use InputSource("http://...")</code>.
 *
 * @param in the input source
 */
public void setParameterXMLInput(InputSource in) {
    paramReader = new ParameterReader(this, in);
}

/**
 * Reads an XML file and builds the contents of this report. Uses a
 * <code>ReportReader</code>.
 *
 * @param f a report XML file
 */
public void read(File f) throws Exception {
    new ReportReader(this).read(f);
}

/**
 * Reads an XML stream using a <code>org.xml.sax.InputSource</code> and
 * builds the contents of this report. Uses a <code>ReportReader</code>.
 * To specify a URL, use <code>new InputSource("http://...")</code>.
 *
 * @param in a reader
 * @see ReportReader#read(org.xml.sax.InputSource)
 */
public void read(org.xml.sax.InputSource in) throws Exception {
    new ReportReader(this).read(in);
}

/**
 * Writes the contents of this report as an XML file.
 *
 * @param fileName a file name string
 */
public void writeFile(String fileName) {
    XMLWriter out = null;
    try {
  out = new XMLWriter(new OutputStreamWriter(new FileOutputStream(fileName),
                  XML_JAVA_ENCODING));
  writeXML(out);
  out.close();
    }
    catch (IOException ioe) {
  ErrorHandler.error(I18N.get("Report.write_err") + ' ' + fileName, ioe,
         I18N.get("Report.write_err_title"));
    }
    finally {
  if (out != null) out.close();
    }
}

/**
 * Writes the contents of this report as an XML file.
 *
 * @param out an indent writer
 */
public void writeXML(XMLWriter out) {
    writeXMLDecl(out);
    writeComment(out);
    writeReport(out);
}

protected void writeXMLDecl(XMLWriter out) {
    out.xmlDecl(XML_ENCODING_ATTRIBUTE);
}

protected void writeComment(XMLWriter out) {
    out.comment("Generated by DataVision version " + info.Version);
    out.comment(info.URL);
}

protected void writeReport(XMLWriter out) {
    out.startElement("report");
    out.attr("dtd-version", OUTPUT_DTD_VERSION);
    out.attr("name", name);
    out.attr("title", title);
    out.attr("author", author);

    writeDescription(out);
    scripting.writeXML(out);
    writeStartFormula(out);
    paperFormat.writeXML(out);
    defaultField.writeXML(out);
    ListWriter.writeList(out, usercols.values(), "usercols");
    dataSource.writeXML(out);
    ListWriter.writeList(out, subreports.values(), "subreports");
    ListWriter.writeList(out, parameters.values(), "parameters");
    ListWriter.writeList(out, formulas.values(), "formulas");
    ListWriter.writeList(out, reportHeaders.sections(), "headers");
    ListWriter.writeList(out, reportFooters.sections(), "footers");
    writePage(out);
    ListWriter.writeList(out, groups, "groups");
    ListWriter.writeList(out, details.sections(), "details");

    out.endElement();
}

protected void writeDescription(XMLWriter out) {
    out.cdataElement("description", description);
}

protected void writeStartFormula(XMLWriter out) {
    if (startFormula != null)
  startFormula.writeXML(out);
}

protected void writePage(XMLWriter out) {
    if (pageHeaders.isEmpty() && pageFooters.isEmpty())
  return;

    out.startElement("page");
    ListWriter.writeList(out, pageHeaders.sections(), "headers");
    ListWriter.writeList(out, pageFooters.sections(), "footers");
    out.endElement();
}

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.