Package org.geomajas.layer.wms

Source Code of org.geomajas.layer.wms.WmsLayer$Resolution

/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.layer.wms;

import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import javax.annotation.PostConstruct;

import org.geomajas.configuration.Parameter;
import org.geomajas.configuration.RasterLayerInfo;
import org.geomajas.configuration.client.ScaleInfo;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Crs;
import org.geomajas.geometry.CrsTransform;
import org.geomajas.global.Api;
import org.geomajas.global.ExceptionCode;
import org.geomajas.global.GeomajasException;
import org.geomajas.layer.LayerException;
import org.geomajas.layer.RasterLayer;
import org.geomajas.layer.feature.Attribute;
import org.geomajas.layer.feature.Feature;
import org.geomajas.layer.feature.attribute.StringAttribute;
import org.geomajas.layer.tile.RasterTile;
import org.geomajas.layer.tile.TileCode;
import org.geomajas.service.DispatcherUrlService;
import org.geomajas.service.DtoConverterService;
import org.geomajas.service.GeoService;
import org.geotools.GML;
import org.geotools.GML.Version;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;

/**
* <p>
* Layer model for accessing raster data from WMS servers. It has support for most WMS versions: up to 1.3.0. When using
* this layer, note that the following fields are required:
* <ul>
* <li><b>baseWmsUrl</b>: The base URL to the WMS server.</li>
* <li><b>format</b>: The format for the returned images.</li>
* <li><b>version</b>: The version of WMS to use.</li>
* <li><b>styles</b>: The styles to use when rendering the WMS images.</li>
* <li><b>useProxy</b>: Set to true to use a proxy for rendering the WMS, hiding the URL from the client.
* This automatically happens when setting the authentication object.</li>
* </ul>
* There always is the option of adding additional parameters to the WMS GetMap requests, by filling the
* <code>parameters</code> list. Such parameters could include optional WMS GetMap parameters, such as "transparency",
* but also "user" and "password".
* </p>
* <p>
* This layer also supports BASIC and DIGEST authentication. To use this functionality, set the
* <code>authentication</code> field.
* </p>
*
* @author Jan De Moerloose
* @author Pieter De Graef
* @author Oliver May
* @author Joachim Van der Auwera
* @since 1.7.1
*/
@Api
public class WmsLayer implements RasterLayer, LayerFeatureInfoSupport {

  private final Logger log = LoggerFactory.getLogger(WmsLayer.class);

  private List<Resolution> resolutions = new ArrayList<Resolution>();

  // @NotNull this seems to cause problems, it is tested in @PostConstruct anyway
  private String baseWmsUrl;

  private String format = "image/png";
  private String version = "1.1.1";
  private String styles = "";
  private List<Parameter> parameters;

  private boolean enableFeatureInfoSupport;

  private RasterLayerInfo layerInfo;

  private Crs crs;

  private String id;

  private WmsAuthentication authentication;

  private boolean useProxy;

  @Autowired
  private GeoService geoService;

  @Autowired
  private WmsHttpService httpService;

  @Autowired
  private DtoConverterService converterService;

  @Autowired(required = false)
  private DispatcherUrlService dispatcherUrlService;

  /**
   * Return the layers identifier.
   *
   * @return layer id
   */
  public String getId() {
    return id;
  }

  /**
   * Set the layer identifier.
   *
   * @param id layer id
   */
  public void setId(String id) {
    this.id = id;
  }

  /**
   * Return the layer info object.
   *
   * @since 1.7.1
   */
  public RasterLayerInfo getLayerInfo() {
    return layerInfo;
  }

  /**
   * Get coordinate reference system for this layer.
   *
   * @return Coordinate reference system for this layer.
   * @deprecated use {@link org.geomajas.layer.LayerService#getCrs(org.geomajas.layer.Layer)}
   */
  @Deprecated
  public CoordinateReferenceSystem getCrs() {
    return crs;
  }

  @PostConstruct
  protected void postConstruct() throws GeomajasException {
    if (null == baseWmsUrl) {
      throw new GeomajasException(ExceptionCode.PARAMETER_MISSING, "baseWmsUrl");
    }

    crs = geoService.getCrs2(getLayerInfo().getCrs());

    // calculate resolutions
    List<ScaleInfo> scales = layerInfo.getZoomLevels();
    if (scales != null) {
      List<Double> r = new ArrayList<Double>();
      for (ScaleInfo scale : scales) {
        r.add(1. / scale.getPixelPerUnit());
      }

      // sort in decreasing order !!!
      Collections.sort(r);
      Collections.reverse(r);

      int level = 0;
      for (double resolution : r) {
        resolutions.add(
            new Resolution(resolution, level++, layerInfo.getTileWidth(), layerInfo.getTileHeight()));
      }
    }
  }

  /**
   * Set the layer configuration.
   *
   * @param layerInfo layer information
   * @throws LayerException oops
   * @since 1.7.1
   */
  @Api
  public void setLayerInfo(RasterLayerInfo layerInfo) throws LayerException {
    this.layerInfo = layerInfo;
  }

  /** {@inheritDoc}. */
  public List<Feature> getFeaturesByLocation(Coordinate layerCoordinate, double layerScale, int pixelTolerance)
      throws LayerException {
    if (!isEnableFeatureInfoSupport()) {
      return Collections.emptyList();
    }

    List<Feature> features = new ArrayList<Feature>();

    Resolution bestResolution = getResolutionForScale(layerScale);
    RasterGrid grid = getRasterGrid(new Envelope(layerCoordinate), bestResolution.getTileWidth(),
        bestResolution.getTileHeight(), layerScale);
    int x = (int) (((layerCoordinate.x - grid.getLowerLeft().x) * bestResolution.getTileWidthPx())
        / grid.getTileWidth());
    int y = (int) (bestResolution.getTileHeightPx() - (((layerCoordinate.y - grid.getLowerLeft().y) *
        bestResolution .getTileHeightPx()) / grid.getTileHeight()));

    Bbox layerBox = new Bbox(grid.getLowerLeft().x, grid.getLowerLeft().y,
        grid.getTileWidth(), grid.getTileHeight());

    InputStream stream = null;
    try {
      String url = formatGetFeatureInfoUrl(bestResolution.getTileWidthPx(), bestResolution.getTileHeightPx(),
          layerBox, x, y);
      log.debug("getFeaturesByLocation: {} {} {} {}",
          new Object[] {layerCoordinate, layerScale, pixelTolerance, url});
      GML gml = new GML(Version.GML3);

      stream = httpService.getStream(url, getAuthentication());
      FeatureCollection<?, SimpleFeature> collection = gml.decodeFeatureCollection(stream);
      FeatureIterator<SimpleFeature> it = collection.features();

      while (it.hasNext()) {
        features.add(toDto(it.next()));
      }
    } catch (LayerException le) {
      throw le;
    } catch (Exception e) {
      throw new LayerException(e, ExceptionCode.UNEXPECTED_PROBLEM);
    } finally {
      if (null != stream) {
        try {
          stream.close();
        } catch (IOException ioe) {
          // ignore, closing anyway
        }
      }
    }

    return features;
  }

  private Feature toDto(SimpleFeature feature) {
    if (feature == null) {
      return null;
    }
    Feature dto = new Feature(feature.getID());

    HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();

    for (AttributeDescriptor desc : feature.getType().getAttributeDescriptors()) {
      Object obj = feature.getAttribute(desc.getName());
      if (null != obj) {
        attributes.put(desc.getLocalName(), new StringAttribute(obj.toString()));
      }
    }
    dto.setAttributes(attributes);
    dto.setId(feature.getID());

    dto.setUpdatable(false);
    dto.setDeletable(false);
    return dto;

  }

  /**
   * Paints the specified bounds optimized for the specified scale in pixel/unit.
   *
   * @param targetCrs Coordinate reference system used for bounds
   * @param bounds bounds to request images for
   * @param scale scale or zoom level (unit?)
   * @return a list of raster images that covers the bounds
   * @throws GeomajasException oops
   */
  public List<RasterTile> paint(CoordinateReferenceSystem targetCrs, Envelope bounds, double scale)
      throws GeomajasException {
    Envelope layerBounds = bounds;
    double layerScale = scale;
    CrsTransform layerToMap = null;
    boolean needTransform = !crs.equals(targetCrs);

    try {
      // We don't necessarily need to split into same CRS and different CRS cases, the latter implementation uses
      // identity transform if crs's are equal for map and layer but might introduce bugs in rounding and/or
      // conversions.
      if (needTransform) {
        layerToMap = geoService.getCrsTransform(crs, targetCrs);
        CrsTransform mapToLayer = geoService.getCrsTransform(targetCrs, crs);

        // Translate the map coordinates to layer coordinates, assumes equal x-y orientation
        layerBounds = geoService.transform(bounds, mapToLayer);
        layerScale = bounds.getWidth() * scale / layerBounds.getWidth();
      }
    } catch (MismatchedDimensionException e) {
      throw new GeomajasException(e, ExceptionCode.RENDER_DIMENSION_MISMATCH);
    }
    layerBounds = clipBounds(layerBounds);
    if (layerBounds.isNull()) {
      return new ArrayList<RasterTile>(0);
    }

    // Grid is in layer coordinate space!
    Resolution bestResolution = getResolutionForScale(layerScale);
    RasterGrid grid = getRasterGrid(layerBounds, bestResolution.getTileWidth(),
        bestResolution.getTileHeight(), layerScale);

    // We calculate the first tile's screen box with this assumption
    List<RasterTile> result = new ArrayList<RasterTile>();
    for (int i = grid.getXmin(); i < grid.getXmax(); i++) {
      for (int j = grid.getYmin(); j < grid.getYmax(); j++) {
        double x = grid.getLowerLeft().x + (i - grid.getXmin()) * grid.getTileWidth();
        double y = grid.getLowerLeft().y + (j - grid.getYmin()) * grid.getTileHeight();
        // layer coordinates
        Bbox worldBox;
        Bbox layerBox;
        if (needTransform) {
          layerBox = new Bbox(x, y, grid.getTileWidth(), grid.getTileHeight());
          // Transforming back to map coordinates will only result in a proper grid if the transformation
          // is nearly affine
          worldBox = geoService.transform(layerBox, layerToMap);
        } else {
          worldBox = new Bbox(x, y, grid.getTileWidth(), grid.getTileHeight());
          layerBox = worldBox;
        }
        // Rounding to avoid white space between raster tiles lower-left becomes upper-left in inverted y-space
        Bbox screenBox = new Bbox(Math.round(scale * worldBox.getX()), -Math.round(scale * worldBox.getMaxY()),
            Math.round(scale * worldBox.getMaxX()) - Math.round(scale * worldBox.getX()), Math.round(scale
            * worldBox.getMaxY())
            - Math.round(scale * worldBox.getY()));

        RasterTile image = new RasterTile(screenBox, getId() + "." + bestResolution.getLevel() + "." + i
            + "," + j);

        image.setCode(new TileCode(bestResolution.getLevel(), i, j));
        String url = formatUrl(bestResolution.getTileWidthPx(), bestResolution.getTileHeightPx(), layerBox);
        image.setUrl(url);
        result.add(image);
      }
    }

    return result;
  }

  private String getWmsTargetUrl() {
    if (useProxy || null != authentication) {
      if (null != dispatcherUrlService) {
        String url = dispatcherUrlService.getDispatcherUrl();
        if (!url.endsWith("/")) {
          url += "/";
        }
        return url + "wms/" + getId() + "/";
      } else {
        return "./d/wms/" + getId() + "/";
      }
    } else {
      return baseWmsUrl;
    }
  }

  private String formatGetFeatureInfoUrl(int width, int height, Bbox box, int x, int y) throws GeomajasException {
    // Always use direct url
    StringBuilder url = formatBaseUrl(baseWmsUrl, width, height, box);
    String layers = getId();
    if (layerInfo.getDataSourceName() != null) {
      layers = layerInfo.getDataSourceName();
    }
    url.append("&QUERY_LAYERS=");
    url.append(layers);
    url.append("&request=GetFeatureInfo");
    url.append("&X=");
    url.append(Integer.toString(x));
    url.append("&Y=");
    url.append(Integer.toString(y));
    url.append("&INFO_FORMAT=application/vnd.ogc.gml");
    return url.toString();
  }

  private String formatUrl(int width, int height, Bbox box) throws GeomajasException {
    StringBuilder url = formatBaseUrl(getWmsTargetUrl(), width, height, box);
    url.append("&request=GetMap");
    return url.toString();
  }

  /**
   * Build the base part of the url (doesn't change for getMap or getFeatureInfo requests).
   *
   * @param targetUrl base url
   * @param width image width
   * @param height image height
   * @param box bounding box
   * @return base WMS url
   * @throws GeomajasException missing parameter
   */
  private StringBuilder formatBaseUrl(String targetUrl, int width, int height, Bbox box) throws GeomajasException {
    StringBuilder url = new StringBuilder(targetUrl);
    int pos = url.lastIndexOf("?");
    if (pos > 0) {
      url.append("&SERVICE=WMS");
    } else {
      url.append("?SERVICE=WMS");
    }
    String layers = getId();
    if (layerInfo.getDataSourceName() != null) {
      layers = layerInfo.getDataSourceName();
    }
    url.append("&layers=");
    url.append(layers);
    url.append("&WIDTH=");
    url.append(Integer.toString(width));
    url.append("&HEIGHT=");
    url.append(Integer.toString(height));
    DecimalFormat decimalFormat = new DecimalFormat(); // create new as this is not thread safe
    decimalFormat.setDecimalSeparatorAlwaysShown(false);
    decimalFormat.setGroupingUsed(false);
    decimalFormat.setMinimumFractionDigits(0);
    decimalFormat.setMaximumFractionDigits(100);
    DecimalFormatSymbols symbols = new DecimalFormatSymbols();
    symbols.setDecimalSeparator('.');
    decimalFormat.setDecimalFormatSymbols(symbols);

    url.append("&bbox=");
    url.append(decimalFormat.format(box.getX()));
    url.append(",");
    url.append(decimalFormat.format(box.getY()));
    url.append(",");
    url.append(decimalFormat.format(box.getMaxX()));
    url.append(",");
    url.append(decimalFormat.format(box.getMaxY()));
    url.append("&format=");
    url.append(format);
    url.append("&version=");
    url.append(version);
    if ("1.3.0".equals(version)) {
      url.append("&crs=");
    } else {
      url.append("&srs=");
    }
    url.append(layerInfo.getCrs());
    url.append("&styles=" );
    url.append(styles);
    if (null != parameters) {
      for (Parameter p : parameters) {
        url.append("&");
        url.append(p.getName());
        url.append("=");
        url.append(p.getValue());
      }
    }
    return url;
  }

  private Resolution getResolutionForScale(double scale) {
    if (null == resolutions || resolutions.size() == 0) {
      return calculateBestQuadTreeResolution(scale);
    } else {
      double screenResolution = 1.0 / scale;
      if (screenResolution >= resolutions.get(0).getResolution()) {
        return resolutions.get(0);
      } else if (screenResolution <= resolutions.get(resolutions.size() - 1).getResolution()) {
        return resolutions.get(resolutions.size() - 1);
      } else {
        for (int i = 0; i < resolutions.size() - 1; i++) {
          Resolution upper = resolutions.get(i);
          Resolution lower = resolutions.get(i + 1);
          if (screenResolution <= upper.getResolution() && screenResolution >= lower.getResolution()) {
            if ((upper.getResolution() - screenResolution) >
                2 * (screenResolution - lower.getResolution())) {
              return lower;
            } else {
              return upper;
            }
          }
        }
      }
    }
    // should not occur !!!!
    return resolutions.get(resolutions.size() - 1);
  }

  private Resolution calculateBestQuadTreeResolution(double scale) {
    double screenResolution = 1.0 / scale;
    // based on quad tree created by subdividing the maximum extent
    Bbox bbox = layerInfo.getMaxExtent();
    double maxWidth = bbox.getWidth();
    double maxHeight = bbox.getHeight();

    int tileWidth = layerInfo.getTileWidth();
    int tileHeight = layerInfo.getTileHeight();

    Resolution upper = new Resolution(Math.max(maxWidth / tileWidth, maxHeight / tileHeight), 0, tileWidth,
        tileHeight);
    if (screenResolution >= upper.getResolution()) {
      return upper;
    } else {
      int level = 0;
      Resolution lower = upper; // set value to avoid possible NPE
      while (screenResolution < upper.getResolution()) {
        lower = upper;
        level++;
        double width = maxWidth / Math.pow(2, level);
        double height = maxHeight / Math.pow(2, level);
        upper = new Resolution(Math.max(width / tileWidth, height / tileHeight), level, tileWidth,
            tileHeight);
      }
      if ((screenResolution - upper.getResolution()) > 2 * (lower.getResolution() - screenResolution)) {
        return lower;
      } else {
        return upper;
      }
    }
  }

  private RasterGrid getRasterGrid(Envelope bounds, double width, double height, double scale) {
    // slightly adjust the width and height so it becomes integer for the
    // current scale
    double realWidth = Math.round(width * scale) / scale;
    double realHeight =  Math.round(height * scale) / scale;

    Bbox bbox = getLayerInfo().getMaxExtent();
    int ymin = (int) Math.floor((bounds.getMinY() - bbox.getY()) / realHeight);
    int ymax = (int) Math.ceil((bounds.getMaxY() - bbox.getY()) / realHeight);
    int xmin = (int) Math.floor((bounds.getMinX() - bbox.getX()) / realWidth);
    int xmax = (int) Math.ceil((bounds.getMaxX() - bbox.getX()) / realWidth);
    // same adjustment for corner
    double realXmin = ((int) (bbox.getX() * scale)) / scale;
    double realYmin = ((int) (bbox.getY() * scale)) / scale;
    Coordinate lowerLeft = new Coordinate(realXmin + xmin * realWidth, realYmin + ymin * realHeight);
    return new RasterGrid(lowerLeft, xmin, ymin, xmax, ymax, realWidth, realHeight);
  }

  private Envelope clipBounds(Envelope bounds) {
    Envelope maxExtent = converterService.toInternal(layerInfo.getMaxExtent());
    return bounds.intersection(maxExtent);
  }

  public String getBaseWmsUrl() {
    return baseWmsUrl;
  }

  /**
   * Set the base URL to the WMS server.
   *
   * @param baseWmsUrl base URL for the WMS server
   * @since 1.7.1
   */
  @Api
  public void setBaseWmsUrl(String baseWmsUrl) {
    this.baseWmsUrl = baseWmsUrl;
  }

  public String getFormat() {
    return format;
  }

  /**
   * Set file format to request.
   *
   * @param format file format. For allowed values, check your WMS server.
   * @since 1.7.1
   */
  @Api
  public void setFormat(String format) {
    this.format = format;
  }

  /**
   * Set WMS version to use.
   *
   * @param version WMS version. For allowed values, check your WMS server.
   * @since 1.7.1
   */
  @Api
  public void setVersion(String version) {
    this.version = version;
  }

  /**
   * Set the styles.
   *
   * @param styles styles. For allowed values, check your WMS server.
   * @since 1.7.1
   */
  @Api
  public void setStyles(String styles) {
    this.styles = styles;
  }

  /**
   * Set additional parameters to include in all WMS <code>getMap</code> requests.
   *
   * @param parameters parameters. For possible keys and values, check your WMS server.
   * @since 1.7.1
   */
  @Api
  public void setParameters(List<Parameter> parameters) {
    this.parameters = parameters;
  }

  /**
   * Get the authentication object.
   *
   * @return authentication object
   */
  public WmsAuthentication getAuthentication() {
    return authentication;
  }

  /**
   * <p>
   * Set the authentication object. This configuration object provides support for basic and digest HTTP
   * authentication on the WMS server. If no HTTP authentication is required, leave this empty.
   * </p>
   * <p>
   * Note that there is still the option of adding a user name and password as HTTP parameters, as some WMS server
   * support. To do that, just add <code>parameters</code>.
   * </p>
   *
   * @param authentication authentication object
   * @since 1.8.0
   */
  @Api
  public void setAuthentication(WmsAuthentication authentication) {
    this.authentication = authentication;
  }

  /**
   * Set whether the WMS request should use a proxy. This is automatically done when the authentication object is set.
   * When the WMS request is proxied, the credentials and WMS base address are hidden from the client.
   *
   * @param useProxy true when request needs to use the proxy
   * @since 1.8.0
   */
  @Api
  public void setUseProxy(boolean useProxy) {
    this.useProxy = useProxy;
  }

  /**
   * Set whether the WMS layer should support feature info support. This allows to retrieve feature info from a
   * raster layer. This only makes sense if the WMS layer is based on some kind of feature store like a database.
   *
   * @param enableFeatureInfoSupport whether feature info support is enabled for this layer
   * @since 1.9.0
   */
  @Api
  public void setEnableFeatureInfoSupport(boolean enableFeatureInfoSupport) {
    this.enableFeatureInfoSupport = enableFeatureInfoSupport;
  }

  /**
   * Get whether the WMS layer should support feature info support.
   *
   * @return the enableFeatureInfoSupport true if feature info support is enabled
   */
  public boolean isEnableFeatureInfoSupport() {
    return enableFeatureInfoSupport;
  }

  /**
   * Grid definition for a WMS layer. It is used internally in the WMS layer.
   *
   * @author Jan De Moerloose
   * @author Pieter De Graef
   */
  private static class RasterGrid {

    private Coordinate lowerLeft;

    private int xmin;

    private int ymin;

    private int xmax;

    private int ymax;

    private double tileWidth;

    private double tileHeight;

    RasterGrid(Coordinate lowerLeft, int xmin, int ymin, int xmax, int ymax, double tileWidth, double tileHeight) {
      super();
      this.lowerLeft = lowerLeft;
      this.xmin = xmin;
      this.ymin = ymin;
      this.xmax = xmax;
      this.ymax = ymax;
      this.tileWidth = tileWidth;
      this.tileHeight = tileHeight;
    }

    public Coordinate getLowerLeft() {
      return lowerLeft;
    }

    public double getTileHeight() {
      return tileHeight;
    }

    public double getTileWidth() {
      return tileWidth;
    }

    public int getXmax() {
      return xmax;
    }

    public int getXmin() {
      return xmin;
    }

    public int getYmax() {
      return ymax;
    }

    public int getYmin() {
      return ymin;
    }
  }

  /**
   * Single resolution definition for a WMS layer. This class is used internally in the WMS layer, and therefore has
   * no public constructors.
   *
   * @author Jan De Moerloose
   * @author Pieter De Graef
   */
  private static class Resolution {

    private double resolution;

    private int level;

    private int tileWidth;

    private int tileHeight;

    /**
     * Constructor that immediately requires all fields.
     *
     * @param resolution The actual resolution value. This is the reverse of the scale.
     * @param level The level in the quad tree.
     * @param tileWidth The width of a tile at the given tile level.
     * @param tileHeight The height of a tile at the given tile level.
     */
    Resolution(double resolution, int level, int tileWidth, int tileHeight) {
      this.resolution = resolution;
      this.level = level;
      this.tileWidth = tileWidth;
      this.tileHeight = tileHeight;
    }

    public int getLevel() {
      return level;
    }

    public int getTileHeightPx() {
      return tileHeight;
    }

    public int getTileWidthPx() {
      return tileWidth;
    }

    public double getTileHeight() {
      return tileHeight * resolution;
    }

    public double getTileWidth() {
      return tileWidth * resolution;
    }

    public double getResolution() {
      return resolution;
    }
  }

}
TOP

Related Classes of org.geomajas.layer.wms.WmsLayer$Resolution

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.