Package com.positive.charts.renderer.xy

Source Code of com.positive.charts.renderer.xy.AbstractXYItemRenderer

package com.positive.charts.renderer.xy;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;

import com.positive.charts.axis.ValueAxis;
import com.positive.charts.block.RectUtils;
import com.positive.charts.data.Range;
import com.positive.charts.data.util.DatasetUtilities;
import com.positive.charts.data.xy.XYDataset;
import com.positive.charts.entity.EntityCollection;
import com.positive.charts.entity.XYItemEntity;
import com.positive.charts.event.RendererChangeEvent;
import com.positive.charts.labels.ItemLabelPosition;
import com.positive.charts.labels.StandardXYSeriesLabelGenerator;
import com.positive.charts.labels.XYItemLabelGenerator;
import com.positive.charts.labels.XYSeriesLabelGenerator;
import com.positive.charts.legend.LegendItem;
import com.positive.charts.legend.LegendItemCollection;
import com.positive.charts.plot.DrawingSupplier;
import com.positive.charts.plot.Plot;
import com.positive.charts.plot.PlotOrientation;
import com.positive.charts.plot.PlotRenderingInfo;
import com.positive.charts.plot.xy.XYPlot;
import com.positive.charts.renderer.AbstractRenderer;
import com.positive.charts.util.DrawingAssets;
import com.positive.charts.util.RectangleUtil;
import com.positive.charts.util.Stroke;
import com.positive.charts.util.TextUtilities;
import com.positive.colorchecker.StaticColorChecker;

/**
* A base class that can be used to create new {@link XYItemRenderer}
* implementations.
*/
public abstract class AbstractXYItemRenderer extends AbstractRenderer implements
    XYItemRenderer {

  /** The plot. */
  protected XYPlot plot;

  /** The item label generator for ALL series. */
  private XYItemLabelGenerator itemLabelGenerator;

  /** A list of item label generators (one per series). */
  private final List itemLabelGeneratorList;

  /** The base item label generator. */
  private XYItemLabelGenerator baseItemLabelGenerator;

  private int defaultEntityRadius;

  private XYSeriesLabelGenerator legendItemLabelGenerator;

  private XYSeriesLabelGenerator legendItemToolTipGenerator;

  private XYSeriesLabelGenerator legendItemURLGenerator;

  /**
   * Creates a renderer where the tooltip generator and the URL generator are
   * both <code>null</code>.
   */
  protected AbstractXYItemRenderer() {
    this.itemLabelGenerator = null;
    this.itemLabelGeneratorList = new ArrayList();
    this.defaultEntityRadius = 3;
    this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator();
  }

  /**
   * Adds an entity to the collection.
   *
   * @param entities
   *            the entity collection being populated.
   * @param area
   *            the entity area (if <code>null</code> a default will be used).
   * @param dataset
   *            the dataset.
   * @param series
   *            the series.
   * @param item
   *            the item.
   * @param entityX
   *            the entity's center x-coordinate in user space (only used if
   *            <code>area</code> is <code>null</code>).
   * @param entityY
   *            the entity's center y-coordinate in user space (only used if
   *            <code>area</code> is <code>null</code>).
   */
  protected void addEntity(final EntityCollection entities,
      final Rectangle area, final XYDataset dataset, final int series,
      final int item, final double entityX, final double entityY) {
    if (!this.getItemCreateEntity(series, item)) {
      return;
    }
    Rectangle hotspot = area;
    if (hotspot == null) {
      final double w = this.defaultEntityRadius * 2;
      if (this.getPlot().getOrientation() == PlotOrientation.VERTICAL) {
        hotspot = RectUtils.doubleRect(entityX
            - this.defaultEntityRadius, entityY
            - this.defaultEntityRadius, w, w);
      } else {
        hotspot = RectUtils.doubleRect(entityY
            - this.defaultEntityRadius, entityX
            - this.defaultEntityRadius, w, w);
      }
    }
    final String tip = null;
    // XYToolTipGenerator generator = getToolTipGenerator(series, item);
    // if (generator != null) {
    // tip = generator.generateToolTip(dataset, series, item);
    // }
    final String url = null;

    final XYItemEntity entity = new XYItemEntity(hotspot, dataset, series,
        item, tip, url);
    entities.add(entity);
  }

  /**
   * Draws a grid line against the range axis.
   *
   * @param g2
   *            the graphics device.
   * @param plot
   *            the plot.
   * @param axis
   *            the value axis.
   * @param dataArea
   *            the area for plotting data (not yet adjusted for any 3D
   *            effect).
   * @param value
   *            the value at which the grid line should be drawn.
   */
  public void drawDomainGridLine(final GC g2, final XYPlot plot,
      final ValueAxis axis, final Rectangle dataArea, final double value) {

    final Range range = axis.getRange();
    if (!range.contains(value)) {
      return;
    }

    final PlotOrientation orientation = plot.getOrientation();
    final int v = (int) axis.valueToJava2D(value, dataArea, plot
        .getDomainAxisEdge());

    final Color color = plot.getDomainGridlinePaint();
    final Stroke stroke = plot.getDomainGridlineStroke();
    // g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
    // g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);

    if (color != null) {
      g2.setForeground(color);
    }
    if (stroke != null) {
      stroke.set(g2);
    }
    if (orientation == PlotOrientation.HORIZONTAL) {
      g2.drawLine(RectangleUtil.getMinX(dataArea), v, RectangleUtil
          .getMaxX(dataArea), v);
    } else if (orientation == PlotOrientation.VERTICAL) {
      g2.drawLine(v, RectangleUtil.getMinY(dataArea), v, RectangleUtil
          .getMaxY(dataArea));
    }
  }

  /**
   * Draws an item label.
   *
   * @param g2
   *            the graphics device.
   * @param orientation
   *            the orientation.
   * @param dataset
   *            the dataset.
   * @param series
   *            the series index (zero-based).
   * @param item
   *            the item index (zero-based).
   * @param x
   *            the x coordinate (in Java2D space).
   * @param y
   *            the y coordinate (in Java2D space).
   * @param negative
   *            indicates a negative value (which affects the item label
   *            position).
   */
  protected void drawItemLabel(final GC g2,
      final PlotOrientation orientation, final XYDataset dataset,
      final int series, final int item, final double x, final double y,
      final boolean negative) {

    final XYItemLabelGenerator generator = this.getItemLabelGenerator(
        series, item);
    if (generator != null) {
      final Font labelFont = this.getItemLabelFont(series, item);
      // Paint paint = getItemLabelPaint(series, item);
      // g2.setPaint(paint);

      // Color color = getColor(ITEM_LABEL_COLOR);
      // g2.setForeground(color);
      g2.setFont(labelFont);
      final String label = generator.generateLabel(dataset, series, item);

      // get the label position..
      ItemLabelPosition position = null;
      if (!negative) {
        position = this.getPositiveItemLabelPosition(series, item);
      } else {
        position = this.getNegativeItemLabelPosition(series, item);
      }

      // work out the label anchor point...
      final Point anchorPoint = this.calculateLabelAnchorPoint(position
          .getItemLabelAnchor(), (int) x, (int) y, orientation);
      TextUtilities.drawRotatedString(label, g2, anchorPoint.x,
          anchorPoint.y, position.getTextAnchor(), position
              .getAngle(), position.getRotationAnchor());
    }

  }

  /**
   * Draws a line perpendicular to the range axis.
   *
   * @param g2
   *            the graphics device.
   * @param plot
   *            the plot.
   * @param axis
   *            the value axis.
   * @param dataArea
   *            the area for plotting data (not yet adjusted for any 3D
   *            effect).
   * @param value
   *            the value at which the grid line should be drawn.
   */
  public void drawRangeLine(final GC g2, final XYPlot plot,
      final ValueAxis axis, final Rectangle dataArea, final double value) {
    final Color color = plot.getDomainGridlinePaint();
    final Stroke stroke = plot.getDomainGridlineStroke();
    // g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
    // g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);

    if (color != null) {
      g2.setForeground(color);
    }
    if (stroke != null) {
      stroke.set(g2);
    }
    this.drawRangeLine(g2, plot, axis, dataArea, value, null, null);
  }

  /**
   * Draws a line perpendicular to the range axis.
   *
   * @param gc
   *            the graphics device.
   * @param plot
   *            the plot.
   * @param axis
   *            the value axis.
   * @param dataArea
   *            the area for plotting data (not yet adjusted for any 3D
   *            effect).
   * @param value
   *            the value at which the grid line should be drawn.
   * @param paint
   *            the paint.
   * @param stroke
   *            the stroke.
   */
  public void drawRangeLine(final GC gc, final XYPlot plot,
      final ValueAxis axis, final Rectangle dataArea, final double value,
      final Color paint, final Stroke stroke) {
    final Range range = axis.getRange();
    if (!range.contains(value)) {
      return;
    }

    if (paint != null) {
      gc.setForeground(paint);
    }
    if (stroke != null) {
      stroke.set(gc);
    }

    final PlotOrientation orientation = plot.getOrientation();
    final int v = (int) axis.valueToJava2D(value, dataArea, plot
        .getRangeAxisEdge());
    if (orientation == PlotOrientation.HORIZONTAL) {
      gc.drawLine(v, RectangleUtil.getMinY(dataArea), v, RectangleUtil
          .getMaxY(dataArea));
    } else if (orientation == PlotOrientation.VERTICAL) {
      gc.drawLine(RectangleUtil.getMinX(dataArea), v, RectangleUtil
          .getMaxX(dataArea), v);
    }
  }

  // ITEM LABEL GENERATOR

  /**
   * Fills a band between two values on the axis. This can be used to color
   * bands between the grid lines.
   *
   * @param g2
   *            the graphics device.
   * @param plot
   *            the plot.
   * @param axis
   *            the domain axis.
   * @param dataArea
   *            the data area.
   * @param start
   *            the start value.
   * @param end
   *            the end value.
   */
  public void fillDomainGridBand(final GC gc, final XYPlot plot,
      final ValueAxis axis, final Rectangle dataArea, final double start,
      final double end) {

    final int x1 = (int) axis.valueToJava2D(start, dataArea, plot
        .getDomainAxisEdge());
    final int x2 = (int) axis.valueToJava2D(end, dataArea, plot
        .getDomainAxisEdge());
    // TODO Take account of plot orientation
    final Rectangle band = new Rectangle(x1, RectangleUtil
        .getMinY(dataArea), x2 - x1, dataArea.height);
    final Color color = plot.getColor(Plot.COLOR_DOMAIN_TICK_BAND);

    if (color != null) {
      gc.setBackground(color);
      gc.fillRectangle(band);
    }

  }

  /**
   * Fills a band between two values on the range axis. This can be used to
   * color bands between the grid lines.
   *
   * @param g2
   *            the graphics device.
   * @param plot
   *            the plot.
   * @param axis
   *            the range axis.
   * @param dataArea
   *            the data area.
   * @param start
   *            the start value.
   * @param end
   *            the end value.
   */
  public void fillRangeGridBand(final GC g2, final XYPlot plot,
      final ValueAxis axis, final Rectangle dataArea, final double start,
      final double end) {

    final int y1 = (int) axis.valueToJava2D(start, dataArea, plot
        .getRangeAxisEdge());
    final int y2 = (int) axis.valueToJava2D(end, dataArea, plot
        .getRangeAxisEdge());
    // TODO: need to change the next line to take account of the plot
    // orientation
    final Rectangle band = new Rectangle(RectangleUtil.getMinX(dataArea),
        y2, dataArea.width, y1 - y2);
    final Color paint = plot.getColor(Plot.COLOR_RANGE_TICK_BAND);

    if (paint != null) {
      g2.setBackground(paint);
      g2.fillRectangle(band);
    }
  }

  /**
   * Returns the lower and upper bounds (range) of the x-values in the
   * specified dataset.
   *
   * @param dataset
   *            the dataset (<code>null</code> permitted).
   *
   * @return The range (<code>null</code> if the dataset is <code>null</code>
   *         or empty).
   */
  public Range findDomainBounds(final XYDataset dataset) {
    if (dataset != null) {
      return DatasetUtilities.findDomainBounds(dataset, false);
    } else {
      return null;
    }
  }

  /**
   * Returns the range of values the renderer requires to display all the
   * items from the specified dataset.
   *
   * @param dataset
   *            the dataset (<code>null</code> permitted).
   *
   * @return The range (<code>null</code> if the dataset is <code>null</code>
   *         or empty).
   */
  public Range findRangeBounds(final XYDataset dataset) {
    if (dataset != null) {
      return DatasetUtilities.findRangeBounds(dataset, false);
    } else {
      return null;
    }
  }

  /**
   * Returns the base item label generator.
   *
   * @return The generator (possibly <code>null</code>).
   */
  public XYItemLabelGenerator getBaseItemLabelGenerator() {
    return this.baseItemLabelGenerator;
  }

  /**
   * Returns the radius of the circle used for the default entity area when no
   * area is specified.
   *
   * @return A radius.
   */
  public int getDefaultEntityRadius() {
    return this.defaultEntityRadius;
  }

  /**
   * Returns the drawing assets from the plot.
   *
   * @return The drawing assets
   * @see Plot#getDrawingAssets();
   */
  public DrawingAssets getDrawingAssets() {
    final Plot p = this.getPlot();
    if (p != null) {
      return p.getDrawingAssets();
    }
    return null;
  }

  /**
   * Returns the drawing supplier from the plot.
   *
   * @return The drawing supplier (possibly <code>null</code>).
   */
  public DrawingSupplier getDrawingSupplier() {
    final Plot p = this.getPlot();
    if (p != null) {
      return p.getDrawingSupplier();
    }
    return null;
  }

  /**
   * Returns the label generator for a data item. This implementation simply
   * passes control to the {@link #getSeriesItemLabelGenerator(int)} method.
   * If, for some reason, you want a different generator for individual items,
   * you can override this method.
   *
   * @param row
   *            the row index (zero based).
   * @param column
   *            the column index (zero based).
   *
   * @return The generator (possibly <code>null</code>).
   */
  public XYItemLabelGenerator getItemLabelGenerator(final int row,
      final int column) {
    return this.getSeriesItemLabelGenerator(row);
  }

  /**
   * Returns a default legend item for the specified series. Subclasses should
   * override this method to generate customised items.
   *
   * @param datasetIndex
   *            the dataset index (zero-based).
   * @param series
   *            the series index (zero-based).
   *
   * @return A legend item for the series.
   */
  public LegendItem getLegendItem(final int datasetIndex, final int series) {
    LegendItem result = null;
    final XYPlot xyplot = this.getPlot();
    if (xyplot != null) {
      final XYDataset dataset = xyplot.getDataset(datasetIndex);
      if (dataset != null) {

        final String label = this.legendItemLabelGenerator
            .generateLabel(dataset, series);
        final String description = label;
        String toolTipText = null;
        if (this.getLegendItemToolTipGenerator() != null) {
          toolTipText = this.getLegendItemToolTipGenerator()
              .generateLabel(dataset, series);
        }
        String urlText = null;
        if (this.getLegendItemURLGenerator() != null) {
          urlText = this.getLegendItemURLGenerator().generateLabel(
              dataset, series);
        }
        final Rectangle shape = this.lookupSeriesShape(series);
        final Color paint = this.getSeriesPaint(series);
        final Color outlinePaint = this
            .lookupSeriesOutlinePaint(series);
        final Stroke outlineStroke = this
            .lookupSeriesOutlineStroke(series);
        result = new LegendItem(label, description, toolTipText,
            urlText, shape, paint, outlineStroke, outlinePaint);
        result.setSeriesKey(dataset.getSeriesKey(series));
        result.setSeriesKey(dataset.getSeriesKey(series));
        result.setSeriesIndex(series);
        result.setDataset(dataset);
        result.setDatasetIndex(datasetIndex);
      }
    }
    return result;
  }

  /**
   * Returns the legend item label generator.
   *
   * @return The label generator (never <code>null</code>).
   */
  public XYSeriesLabelGenerator getLegendItemLabelGenerator() {
    return this.legendItemLabelGenerator;
  }

  /**
   * Returns a (possibly empty) collection of legend items for the series that
   * this renderer is responsible for drawing.
   *
   * @return The legend item collection (never <code>null</code>).
   */
  public LegendItemCollection getLegendItems() {
    if (this.plot == null) {
      return new LegendItemCollection();
    }
    final LegendItemCollection result = new LegendItemCollection();
    final int index = this.plot.getIndexOf(this);
    final XYDataset dataset = this.plot.getDataset(index);
    if (dataset != null) {
      final int seriesCount = dataset.getSeriesCount();
      for (int i = 0; i < seriesCount; i++) {
        if (this.isSeriesVisibleInLegend(i)) {
          final LegendItem item = this.getLegendItem(index, i);
          if (item != null) {
            result.add(item);
          }
        }
      }

    }
    return result;
  }

  /**
   * Returns the legend item tool tip generator.
   *
   * @return The tool tip generator (possibly <code>null</code>).
   */
  public XYSeriesLabelGenerator getLegendItemToolTipGenerator() {
    return this.legendItemToolTipGenerator;
  }

  /**
   * Returns the legend item URL generator.
   *
   * @return The URL generator (possibly <code>null</code>).
   */
  public XYSeriesLabelGenerator getLegendItemURLGenerator() {
    return this.legendItemURLGenerator;
  }

  /**
   * Returns the number of passes through the data that the renderer requires
   * in order to draw the chart. Most charts will require a single pass, but
   * some require two passes.
   *
   * @return The pass count.
   */
  public int getPassCount() {
    return 1;
  }

  /**
   * Returns the plot that the renderer is assigned to.
   *
   * @return The plot.
   */
  public XYPlot getPlot() {
    return this.plot;
  }

  /**
   * Returns the item label generator for a series.
   *
   * @param series
   *            the series index (zero based).
   *
   * @return The generator (possibly <code>null</code>).
   */
  public XYItemLabelGenerator getSeriesItemLabelGenerator(final int series) {

    // return the generator for ALL series, if there is one...
    if (this.itemLabelGenerator != null) {
      return this.itemLabelGenerator;
    }

    // otherwise look up the generator table
    XYItemLabelGenerator generator = (XYItemLabelGenerator) this.itemLabelGeneratorList
        .get(series);
    if (generator == null) {
      generator = this.baseItemLabelGenerator;
    }
    return generator;

  }

  // /**
  // * Returns a (possibly empty) collection of legend items for the series
  // that
  // * this renderer is responsible for drawing.
  // *
  // * @return The legend item collection (never <code>null</code>).
  // */
  // public LegendItemCollection getLegendItems() {
  // if (this.plot == null) {
  // return new LegendItemCollection();
  // }
  // LegendItemCollection result = new LegendItemCollection();
  // int index = this.plot.getIndexOf(this);
  // XYDataset dataset = this.plot.getDataset(index);
  // if (dataset != null) {
  // int seriesCount = dataset.getSeriesCount();
  // for (int i = 0; i < seriesCount; i++) {
  // if (isSeriesVisibleInLegend(i)) {
  // LegendItem item = getLegendItem(index, i);
  // if (item != null) {
  // result.add(item);
  // }
  // }
  // }
  //
  // }
  // return result;
  // }

  // /**
  // * Returns a default legend item for the specified series. Subclasses
  // should
  // * override this method to generate customised items.
  // *
  // * @param datasetIndex
  // * the dataset index (zero-based).
  // * @param series
  // * the series index (zero-based).
  // *
  // * @return A legend item for the series.
  // */
  // public LegendItem getLegendItem(int datasetIndex, int series) {
  // LegendItem result = null;
  // XYPlot xyplot = getPlot();
  // if (xyplot != null) {
  // XYDataset dataset = xyplot.getDataset(datasetIndex);
  // if (dataset != null) {
  // String label = this.legendItemLabelGenerator.generateLabel(
  // dataset, series);
  // String description = label;
  // String toolTipText = null;
  // if (getLegendItemToolTipGenerator() != null) {
  // toolTipText = getLegendItemToolTipGenerator()
  // .generateLabel(dataset, series);
  // }
  // String urlText = null;
  // if (getLegendItemURLGenerator() != null) {
  // urlText = getLegendItemURLGenerator().generateLabel(
  // dataset, series);
  // }
  // Shape shape = getSeriesShape(series);
  // Paint paint = getSeriesPaint(series);
  // Paint outlinePaint = getSeriesOutlinePaint(series);
  // Stroke outlineStroke = getSeriesOutlineStroke(series);
  // result = new LegendItem(label, description, toolTipText,
  // urlText, shape, paint, outlineStroke, outlinePaint);
  // result.setSeriesIndex(series);
  // result.setDatasetIndex(datasetIndex);
  // }
  // }
  // return result;
  // }

  /**
   * Initialises the renderer and returns a state object that should be passed
   * to all subsequent calls to the drawItem() method.
   * <P>
   * This method will be called before the first item is rendered, giving the
   * renderer an opportunity to initialise any state information it wants to
   * maintain. The renderer can do nothing if it chooses.
   *
   * @param g2
   *            the graphics device.
   * @param dataArea
   *            the area inside the axes.
   * @param plot
   *            the plot.
   * @param data
   *            the data.
   * @param info
   *            an optional info collection object to return data back to the
   *            caller.
   *
   * @return The renderer state (never <code>null</code>).
   */
  public XYItemRendererState initialise(final GC g2,
      final Rectangle dataArea, final XYPlot plot, final XYDataset data,
      final PlotRenderingInfo info) {
    final XYItemRendererState state = new XYItemRendererState(info);
    return state;
  }

  private Color lookupSeriesOutlinePaint(final int series) {
    return  StaticColorChecker.dublicateColor(SWT.COLOR_BLACK);//Display.getCurrent().getSystemColor(SWT.COLOR_BLACK);
  }

  private Stroke lookupSeriesOutlineStroke(final int series) {
    return new Stroke(1);
  }

  /**
   * Sets the base item label generator and sends a
   * {@link RendererChangeEvent} to all registered listeners.
   *
   * @param generator
   *            the generator (<code>null</code> permitted).
   */
  public void setBaseItemLabelGenerator(final XYItemLabelGenerator generator) {
    this.baseItemLabelGenerator = generator;
    this.notifyListeners(new RendererChangeEvent(this));
  }

  /**
   * Sets the radius of the circle used for the default entity area when no
   * area is specified.
   *
   * @param radius
   *            the radius.
   */
  public void setDefaultEntityRadius(final int radius) {
    this.defaultEntityRadius = radius;
  }

  /**
   * Sets the item label generator for ALL series and sends a
   * {@link RendererChangeEvent} to all registered listeners.
   *
   * @param generator
   *            the generator (<code>null</code> permitted).
   */
  public void setItemLabelGenerator(final XYItemLabelGenerator generator) {
    this.itemLabelGenerator = generator;
    this.notifyListeners(new RendererChangeEvent(this));
  }

  /**
   * Sets the legend item label generator.
   *
   * @param generator
   *            the generator (<code>null</code> not permitted).
   */
  public void setLegendItemLabelGenerator(
      final XYSeriesLabelGenerator generator) {
    if (generator == null) {
      throw new IllegalArgumentException("Null 'generator' argument.");
    }
    this.legendItemLabelGenerator = generator;
  }

  /**
   * Sets the legend item tool tip generator.
   *
   * @param generator
   *            the generator (<code>null</code> permitted).
   */
  public void setLegendItemToolTipGenerator(
      final XYSeriesLabelGenerator generator) {
    this.legendItemToolTipGenerator = generator;
  }

  /**
   * Sets the plot that the renderer is assigned to.
   *
   * @param plot
   *            the plot.
   */
  public void setPlot(final XYPlot plot) {
    this.plot = plot;
  }

  /**
   * Sets the item label generator for a series and sends a
   * {@link RendererChangeEvent} to all registered listeners.
   *
   * @param series
   *            the series index (zero based).
   * @param generator
   *            the generator (<code>null</code> permitted).
   */
  public void setSeriesItemLabelGenerator(final int series,
      final XYItemLabelGenerator generator) {
    this.itemLabelGeneratorList.set(series, generator);
    this.notifyListeners(new RendererChangeEvent(this));
  }

}
TOP

Related Classes of com.positive.charts.renderer.xy.AbstractXYItemRenderer

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.