Package org.pentaho.reporting.engine.classic.core.layout

Source Code of org.pentaho.reporting.engine.classic.core.layout.AbstractRenderer

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.engine.classic.core.layout;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.GroupBody;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.PerformanceTags;
import org.pentaho.reporting.engine.classic.core.ReportDefinition;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.function.ExpressionRuntime;
import org.pentaho.reporting.engine.classic.core.function.ProcessingContext;
import org.pentaho.reporting.engine.classic.core.layout.build.LayoutModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.build.RenderModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.build.ReportRenderModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.output.ContentProcessingException;
import org.pentaho.reporting.engine.classic.core.layout.output.LayoutPagebreakHandler;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessor;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.output.ValidateSafeToStoreStateStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ApplyAutoCommitStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ApplyCachedValuesStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ApplyCommitStep;
import org.pentaho.reporting.engine.classic.core.layout.process.CanvasMajorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.CanvasMinorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.CommitStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ComputeStaticPropertiesProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMajorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMinorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ParagraphLineBreakStep;
import org.pentaho.reporting.engine.classic.core.layout.process.RollbackStep;
import org.pentaho.reporting.engine.classic.core.layout.process.TableValidationStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ValidateModelStep;
import org.pentaho.reporting.engine.classic.core.states.PerformanceMonitorContext;
import org.pentaho.reporting.engine.classic.core.states.ReportStateKey;
import org.pentaho.reporting.engine.classic.core.states.process.ProcessState;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
import org.pentaho.reporting.libraries.base.util.PerformanceLoggingStopWatch;

/**
* The LayoutSystem is a simplified version of the LibLayout-rendering system.
*
* @author Thomas Morgner
* @noinspection HardCodedStringLiteral
*/
public abstract class AbstractRenderer implements Renderer
{
  private class CloseListener implements ChangeListener
  {
    public void stateChanged(final ChangeEvent e)
    {
      close();
    }
  }

  private static final Log logger = LogFactory.getLog(AbstractRenderer.class);

  private RenderModelBuilder renderModelBuilder;

  private ValidateModelStep validateModelStep;
  private ComputeStaticPropertiesProcessStep staticPropertiesStep;
  private ParagraphLineBreakStep paragraphLineBreakStep;
  private InfiniteMinorAxisLayoutStep minorAxisLayoutStep;
  private CanvasMinorAxisLayoutStep canvasMinorAxisLayoutStep;
  private InfiniteMajorAxisLayoutStep majorAxisLayoutStep;
  private CanvasMajorAxisLayoutStep canvasMajorAxisLayoutStep;

  private ValidateSafeToStoreStateStep validateSafeToStoreStateStep;
  private TableValidationStep tableValidationStep;

  private CommitStep commitStep;
  private ApplyCommitStep applyCommitStep;
  private RollbackStep rollbackStep;
  private ApplyAutoCommitStep applyAutoCommitStep;

  private OutputProcessor outputProcessor;

  private int pagebreaks;
  private boolean dirty;
  private ReportStateKey lastStateKey;
  private ApplyCachedValuesStep applyCachedValuesStep;

  private boolean readOnly;
  private boolean paranoidChecks;
  private boolean wrapProgressMarkerInSection;

  private LayoutResult lastValidateResult;
  private PerformanceLoggingStopWatch validateStopWatch;
  private PerformanceLoggingStopWatch paginateStopWatch;
  private PerformanceMonitorContext performanceMonitorContext;

  protected AbstractRenderer(final OutputProcessor outputProcessor)
  {
    this.outputProcessor = outputProcessor;

    this.validateModelStep = new ValidateModelStep();
    this.staticPropertiesStep = new ComputeStaticPropertiesProcessStep();
    this.paragraphLineBreakStep = new ParagraphLineBreakStep();
    this.minorAxisLayoutStep = new InfiniteMinorAxisLayoutStep();
    this.canvasMinorAxisLayoutStep = new CanvasMinorAxisLayoutStep();
    this.majorAxisLayoutStep = new InfiniteMajorAxisLayoutStep();
    this.canvasMajorAxisLayoutStep = new CanvasMajorAxisLayoutStep();
    this.validateSafeToStoreStateStep = new ValidateSafeToStoreStateStep();
    this.applyCachedValuesStep = new ApplyCachedValuesStep();
    this.commitStep = new CommitStep();
    this.applyAutoCommitStep = new ApplyAutoCommitStep();
    this.applyCommitStep = new ApplyCommitStep();
    this.rollbackStep = new RollbackStep();
    this.tableValidationStep = new TableValidationStep();
  }

  protected void initialize()
  {
    this.renderModelBuilder = createRenderModelBuilder();
  }

  protected ReportRenderModelBuilder createRenderModelBuilder()
  {
    return new ReportRenderModelBuilder(createComponentFactory());
  }

  protected RenderComponentFactory createComponentFactory()
  {
    return new DefaultRenderComponentFactory();
  }

  public LayoutModelBuilder getNormalFlowLayoutModelBuilder()
  {
    return renderModelBuilder.getNormalFlowLayoutModelBuilder();
  }

  public int getPageCount()
  {
    return 0;
  }

  protected RenderModelBuilder getRenderModelBuilder()
  {
    return renderModelBuilder;
  }

  protected LogicalPageBox getPageBox()
  {
    return renderModelBuilder.getPageBox();
  }

  public boolean isSafeToStore()
  {
    final LogicalPageBox pageBox = getPageBox();
    if (pageBox == null)
    {
      return true;
    }
    return validateSafeToStoreStateStep.isSafeToStore(pageBox);
  }

  protected OutputProcessorMetaData getMetaData()
  {
    return getOutputProcessor().getMetaData();
  }

  public void setStateKey(final ReportStateKey stateKey)
  {
    renderModelBuilder.updateStateKey(stateKey);
  }

  public OutputProcessor getOutputProcessor()
  {
    return outputProcessor;
  }

  protected boolean isWidowOrphanDefinitionsEncountered()
  {
    return staticPropertiesStep.isWidowOrphanDefinitionsEncountered();
  }

  public void startReport(final ReportDefinition report,
                          final ProcessingContext processingContext,
                          final PerformanceMonitorContext performanceMonitorContext)
  {
    ArgumentNullException.validate("report", report);
    ArgumentNullException.validate("processingContext", processingContext);
    ArgumentNullException.validate("performanceMonitorContext", performanceMonitorContext);

    if (readOnly)
    {
      throw new IllegalStateException();
    }

    this.performanceMonitorContext = performanceMonitorContext;
    this.performanceMonitorContext.addChangeListener(new CloseListener());

    this.validateStopWatch = performanceMonitorContext.createStopWatch(PerformanceTags.REPORT_LAYOUT_VALIDATE);
    this.paginateStopWatch = performanceMonitorContext.createStopWatch(PerformanceTags.REPORT_LAYOUT_PROCESS);

    this.majorAxisLayoutStep.initializePerformanceMonitoring(performanceMonitorContext);
    this.canvasMajorAxisLayoutStep.initializePerformanceMonitoring(performanceMonitorContext);
    this.minorAxisLayoutStep.initializePerformanceMonitoring(performanceMonitorContext);
    this.canvasMinorAxisLayoutStep.initializePerformanceMonitoring(performanceMonitorContext);

    outputProcessor.processingStarted(report, processingContext);

    initializeRendererOnStartReport(processingContext);
    renderModelBuilder.startReport(report, processingContext);
    markDirty();
  }

  protected void initializeRendererOnStartReport(final ProcessingContext processingContext)
  {
    final OutputProcessorMetaData metaData = getMetaData();
    this.paranoidChecks = "true".equals(metaData.getConfiguration().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.layout.ParanoidChecks")) &&
        processingContext.getOutputProcessorMetaData().isFeatureSupported(OutputProcessorFeature.DESIGNTIME) == false;
    this.wrapProgressMarkerInSection = "true".equals(metaData.getConfiguration().getConfigProperty
        ("org.pentaho.reporting.engine.classic.core.legacy.WrapProgressMarkerInSection"));

    staticPropertiesStep.initialize(metaData, processingContext);
    canvasMinorAxisLayoutStep.initialize(metaData, processingContext);
    minorAxisLayoutStep.initialize(metaData);
    canvasMajorAxisLayoutStep.initialize(metaData);
    majorAxisLayoutStep.initialize(metaData);
  }

  public void startSubReport(final ReportDefinition report, final InstanceID insertationPoint)
  {
    if (readOnly)
    {
      throw new IllegalStateException("Renderer is marked read-only");
    }

    renderModelBuilder.startSubReport(report, insertationPoint);
  }

  public void startGroup(final Group group, final Integer predictedStateCount)
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.startGroup(group, 5);
  }

  public void startGroupBody(final GroupBody groupBody, final Integer predictedStateCount)
  {
    if (logger.isDebugEnabled())
    {
      logger.debug("Group-Body: Predicted size: " + predictedStateCount);
    }
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.startGroupBody(groupBody, predictedStateCount);
    markDirty();
  }

  public void startSection(final SectionType type)
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }
    renderModelBuilder.startSection(type);
  }

  public InlineSubreportMarker[] endSection()
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    // todo: Cheap hack for now. Convert this into a real check that figures out whether real changes have been done.
    final RenderModelBuilder.SectionResult result = renderModelBuilder.endSection();
    if (result.isEmpty() == false)
    {
      markDirty();
    }
    return result.getSubreportMarkers();
  }

  public void endGroupBody()
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.endGroupBody();
    markDirty();
  }

  public void endGroup()
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.endGroup();
    markDirty();
  }

  public void endSubReport()
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.endSubReport();
    markDirty();
  }

  public void endReport()
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.endReport();
    markDirty();
  }

  public void addEmptyRootLevelBand()
      throws ReportProcessingException
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.addEmptyRootLevelBand();
  }

  public void addProgressBox()
      throws ReportProcessingException
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    if (wrapProgressMarkerInSection)
    {
      renderModelBuilder.startSection(SectionType.NORMALFLOW);
      renderModelBuilder.addProgressBox();
      renderModelBuilder.endSection();
    }
    else
    {
      renderModelBuilder.addProgressBox();
    }
  }

  public void add(final Band band, final ExpressionRuntime runtime)
      throws ReportProcessingException
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.add(runtime, band);
  }

  public void addToNormalFlow(final Band band,
                              final ExpressionRuntime runtime) throws ReportProcessingException
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.addToNormalFlow(runtime, band);
  }

  public LayoutResult validatePages()
      throws ContentProcessingException
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }
    try
    {
      validateStopWatch.start();

      final LogicalPageBox pageBox = getPageBox();
      if (pageBox == null)
      {
        // StartReport has not been called yet ..
        lastValidateResult = LayoutResult.LAYOUT_UNVALIDATABLE;
        return LayoutResult.LAYOUT_UNVALIDATABLE;
      }

      if (!dirty && lastValidateResult != null)
      {
        return lastValidateResult;
      }

      setLastStateKey(null);
      setPagebreaks(0);
      if (validateModelStep.isLayoutable(pageBox) == false) // STRUCT
      {
        if (logger.isDebugEnabled())
        {
          logger.debug("Content-Ref# " + pageBox.getContentRefCount());
        }
        lastValidateResult = LayoutResult.LAYOUT_UNVALIDATABLE;
        return LayoutResult.LAYOUT_UNVALIDATABLE;
      }

      // These structural processors will skip old nodes. These beasts cannot be cached otherwise.
      tableValidationStep.validate(pageBox); // STRUCT
      paragraphLineBreakStep.compute(pageBox); // STRUCT
      staticPropertiesStep.compute(pageBox); // STRUCT

      minorAxisLayoutStep.compute(pageBox); // VISUAL
      canvasMinorAxisLayoutStep.compute(pageBox); // VISUAL
      majorAxisLayoutStep.compute(pageBox); // VISUAL
      canvasMajorAxisLayoutStep.compute(pageBox); // VISUAL

      if (preparePagination(pageBox) == false)
      {
        return LayoutResult.LAYOUT_UNVALIDATABLE;
      }

      applyCachedValuesStep.compute(pageBox); // STRUCT

      if (isPageFinished())
      {
        lastValidateResult = LayoutResult.LAYOUT_PAGEBREAK;
        return LayoutResult.LAYOUT_PAGEBREAK;
      }
      else
      {
        lastValidateResult = LayoutResult.LAYOUT_NO_PAGEBREAK;
        return LayoutResult.LAYOUT_NO_PAGEBREAK;
      }
    }
    finally
    {
      validateStopWatch.stop(true);
    }
  }

  protected boolean preparePagination(final LogicalPageBox pageBox)
  {
    return true;
  }

  protected void clearDirty()
  {
    dirty = false;
  }

  protected abstract boolean isPageFinished();

  public void processIncrementalUpdate(final boolean performOutput) throws ContentProcessingException
  {
//    dirty = false;
  }

  public boolean processPage(final LayoutPagebreakHandler handler,
                             final Object commitMarker,
                             final boolean performOutput) throws ContentProcessingException
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    try
    {
      paginateStopWatch.start();

      final LogicalPageBox pageBox = getPageBox();
      if (pageBox == null)
      {
        // StartReport has not been called yet ..
//      Log.debug ("PageBox null");
        return false;
      }

      if (dirty == false)
      {
//      Log.debug ("Not dirty");
        return false;
      }

      setLastStateKey(null);
      setPagebreaks(0);
      if (validateModelStep.isLayoutable(pageBox) == false)
      {
        logger.debug("Not layoutable");
        return false;
      }

      // processes the current page
      boolean repeat = true;
      while (repeat)
      {
        if (handler != null)
        {
          // make sure we generate an up-to-date page-footer. This also implies that there
          // are more page-finished than page-started events generated during the report processing.
          handler.pageFinished();
        }

        if (outputProcessor.getMetaData().isFeatureSupported(OutputProcessorFeature.PAGEBREAKS))
        {
          createRollbackInformation();
          applyRollbackInformation();
          performParanoidModelCheck();
        }

        tableValidationStep.validate(pageBox); // STRUCT
        paragraphLineBreakStep.compute(pageBox); // STRUCT
        staticPropertiesStep.compute(pageBox); // VISUAL

        minorAxisLayoutStep.compute(pageBox);
        canvasMinorAxisLayoutStep.compute(pageBox);
        majorAxisLayoutStep.compute(pageBox);
        canvasMajorAxisLayoutStep.compute(pageBox);

        if (preparePagination(pageBox) == false)
        {
          return (pagebreaks > 0);
        }

        applyCachedValuesStep.compute(pageBox);

        repeat = performPagination(handler, performOutput);
      }
      clearDirty();
      return (pagebreaks > 0);
    }
    finally
    {
      validateStopWatch.stop(isOpen());
      paginateStopWatch.stop(isOpen());
    }
  }

  protected abstract boolean performPagination(LayoutPagebreakHandler handler,
                                               final boolean performOutput)
      throws ContentProcessingException;

  /**
   * A hook to allow easier debugging.
   *
   * @param pageBox the current page box.
   * @noinspection NoopMethodInAbstractClass
   */
  protected void debugPrint(final LogicalPageBox pageBox)
  {

  }

  public ReportStateKey getLastStateKey()
  {
    return lastStateKey;
  }

  protected void setLastStateKey(final ReportStateKey lastStateKey)
  {
    this.lastStateKey = lastStateKey;
  }

  protected void setPagebreaks(final int pagebreaks)
  {
    this.pagebreaks = pagebreaks;
  }

  public int getPagebreaks()
  {
    return pagebreaks;
  }

  public boolean isOpen()
  {
    final LogicalPageBox pageBox = getPageBox();
    if (pageBox == null)
    {
      return false;
    }
    return pageBox.isOpen();
  }

  public boolean isValid()
  {
    return readOnly == false;
  }

  public Renderer deriveForStorage()
  {
    try
    {
      final AbstractRenderer renderer = (AbstractRenderer) clone();
      renderer.readOnly = false;
      renderer.renderModelBuilder = renderModelBuilder.deriveForStorage();
      return renderer;
    }
    catch (CloneNotSupportedException cne)
    {
      throw new InvalidReportStateException("Failed to derive Renderer", cne);
    }
  }

  public Renderer deriveForPagebreak()
  {
    try
    {
      final AbstractRenderer renderer = (AbstractRenderer) clone();
      renderer.readOnly = true;
      renderer.renderModelBuilder = renderModelBuilder.deriveForPageBreak();
      return renderer;
    }
    catch (CloneNotSupportedException cne)
    {
      throw new InvalidReportStateException("Failed to derive Renderer", cne);
    }
  }

  public void performParanoidModelCheck()
  {
    if (paranoidChecks)
    {
      renderModelBuilder.performParanoidModelCheck();
    }
  }

  public Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }

  public void addPagebreak()
  {
    if (readOnly)
    {
      throw new IllegalStateException();
    }

    renderModelBuilder.addPageBreak();
  }

  public boolean clearPendingPageStart(final LayoutPagebreakHandler layoutPagebreakHandler)
  {
    // intentionally left empty.
    return false;
  }

  public boolean isCurrentPageEmpty()
  {
    return false;
  }

  public boolean isPageStartPending()
  {
    return false;
  }

  public boolean isDirty()
  {
    return dirty;
  }

  public void createRollbackInformation()
  {
    final LogicalPageBox pageBox = getPageBox();
    if (pageBox != null)
    {
      commitStep.compute(pageBox);
    }
  }

  public void applyRollbackInformation()
  {
    final LogicalPageBox pageBox = getPageBox();
    if (pageBox != null)
    {
      applyCommitStep.compute(pageBox);
    }
  }

  public void validateAfterCommit()
  {
    if (paranoidChecks)
    {
      renderModelBuilder.validateAfterCommit();
    }
  }

  public void rollback()
  {
    readOnly = false;
    final LogicalPageBox pageBox = getPageBox();
    if (pageBox != null)
    {
      rollbackStep.compute(pageBox);
      renderModelBuilder.restoreStateAfterRollback();
      validateAfterCommit();
    }
  }


  public void applyAutoCommit()
  {
    final LogicalPageBox pageBox = getPageBox();
    if (pageBox != null)
    {
      applyAutoCommitStep.compute(pageBox);
    }
  }

  public boolean isPendingPageHack()
  {
    return false;
  }

  protected void markDirty()
  {
    dirty = true;
    lastValidateResult = null;
  }

  public void print()
  {
    if (renderModelBuilder.getPageBox() == null)
    {
      logger.info("Printing impossible - Page-Box empty");
    }
    else
    {
      ModelPrinter.INSTANCE.print(renderModelBuilder.getPageBox());
    }
  }

  public void newPageStarted()
  {
    if (logger.isDebugEnabled())
    {
      logger.debug("================================ CLEAR HEADER AND FOOTER ==================================: " + getPageCount());
    }

    final LogicalPageBox pageBox = getPageBox();
    pageBox.getFooterArea().clear();
    pageBox.getRepeatFooterArea().clear();
    pageBox.getHeaderArea().clear();
    pageBox.getWatermarkArea().clear();
  }

  /**
   * This is a debug helper function. It is not used in normal report runs. It helps debug layouter states
   * and the roll-back system by dumping all layouts into a directory on the file system for automated diffs.
   *
   * @param state
   * @param print
   * @param rollback
   */
  @SuppressWarnings("UnusedDeclaration")
  public void printLayoutStateToFile(final ProcessState state,
                                     final boolean print,
                                     final boolean rollback)
  {
    /*
    if (((state.getSequenceCounter() <= 14440 || state.getSequenceCounter() >= 14445)) ||
        (state.getSequenceCounter() % 1) != 0)
    {
      return;
    }

*/
    String fileName = "test-output/" + state.getSequenceCounter();
    fileName += print ? "-print" : "-paginate";
    fileName += rollback ? "-rb" : "";
    fileName += ".xml";

    FileModelPrinter.print(fileName, getPageBox());
  }

  protected PerformanceLoggingStopWatch getValidateStopWatch()
  {
    return validateStopWatch;
  }

  protected PerformanceLoggingStopWatch getPaginateStopWatch()
  {
    return paginateStopWatch;
  }

  protected PerformanceMonitorContext getPerformanceMonitorContext()
  {
    return performanceMonitorContext;
  }

  protected void close()
  {
    this.majorAxisLayoutStep.close();
    this.canvasMajorAxisLayoutStep.close();
    this.minorAxisLayoutStep.close();
    this.canvasMinorAxisLayoutStep.close();

    validateStopWatch.close();
    paginateStopWatch.close();
  }
}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.layout.AbstractRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.