Package jjil.j2se.ocr

Source Code of jjil.j2se.ocr.EdgePts

/*
/*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU Lesser General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*  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.
*  You should have received a copy of the Lesser GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package jjil.j2se.ocr;

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

import jjil.core.Point;
import jjil.core.TimeTracker;
import jjil.core.Vec2;
import jjil.j2se.algorithm.CircularList;
import jjil.j2se.algorithm.Pair;

/**
* The Class EdgePts manages a list of EdgePt objects which represent
* a boundary (internal or external) of a character to be recognized.
* This is a circular list, with each EdgePt including information that
* refers to the next, including at the end of the list. This class includes
* methods for polygonal approximation of the EdgePt objects, thereby reducing
* the number of EdgePt objects in the list.
*
* @author webb
*/
public class EdgePts extends CircularList<EdgePt> {
   
    /** The Constant FIXED_DIST. Controls the polygonal approximation. */
    private final static int FIXED_DIST = 20;

    /* Controls the recursion step in the Douglas-Peucker polygonal approximation */
    /** The Constant PAR1. */
    private final static int PAR1 = 50;

    /** The EdgePt objects are allocated from a static list to avoid garbage collection in Android, which slows things down quite a bit. This is the maximum number of EdgePt objects that can be used. It must exceed the need for the polygonal approximation etc. to work. */
  private static final int MAX_EDGEPTS = 10000;
 
  /** The array of EdgePt objects from which the actual EdgePt's in use are taken. */
  private static EdgePt srEdgePtStore[] = new EdgePt[MAX_EDGEPTS];
 
  /** The number of EdgePt objects in use at any one time. */
  private static int snEdgePtsUsed = 0;
 
    /** The rectangular bounding box enclosing this collection of EdgePt's. */
    private JessRect rectBounds = null;
   
    /** A time tracker object, used to measure the execution time of expensive operations. */
    private TimeTracker mTimeTracker;
   
    /** Used for vector calculation, so as to avoid creating a new vector *. */
    private Vec2 mvSum = new Vec2(0,0);
   
    /** Used to hold lists of EdgePt objects while splitting */
   
    /* Create an initialize the EdgePt store */
    static {
      for (int i=0; i<srEdgePtStore.length; i++) {
        srEdgePtStore[i] = new EdgePt();
      }
    }
   
    /**
     * The Class SortableCircListIter is used when dividing an EdgePt list into
     * two parts when it is suspected that the list includes two adjacent characters
     * which touch. The edges that cross the split point have to be sorted from top
     * to bottom so we can connect them correctly and not create any figure-8 type
     * loops (i.e., the inside connected to the outside). This is done by adding
     * all edges that cross the split point to an array as members of this class,
     * sorting them, then connecting them from bottom to top. This is guaranteed
     * not to connect inside to outside.
     */
    private class SortableCircListIter implements Comparable<SortableCircListIter> {
       
        /** The mn y. */
        Integer mnY;
       
        /** The mcli. */
        CircularList<EdgePt>.CircListIter mcli;
       
        /**
         * Instantiates a new sortable circular list iterator.
         *
         * @param nY the y position associated with this iterator
         * @param cli the circular list iterator
         */
        SortableCircListIter(int nY, CircularList<EdgePt>.CircListIter cli) {
            this.mnY = nY;
            this.mcli = cli;
        }

        /* (non-Javadoc)
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(SortableCircListIter o) {
            return this.mnY.compareTo(o.mnY);
        }
       
        /**
         * Gets the circular list iterator.
         *
         * @return the circular list iterator
         */
        CircularList<EdgePt>.CircListIter getCircListIter() {
            return this.mcli;
        }
       
        /**
         * Gets the y position associated with this iterator.
         *
         * @return the y position of this iterator
         */
        int getY() {
            return this.mnY;
        }
    }

    /**
     * Instantiates a new edge pts class.
     *
     * @param tt the TimeTracker object used to track expensive operations.
     * May be null if no tracking is needed.
     */
    EdgePts(TimeTracker tt) {
      this.mTimeTracker = tt;
    }

    /**
     * Instantiates a new edge pts, initializing it from a list of Point objects.
     * This is the normal way an EdgePts object is first created.
     *
     * @param lpt the list of points that will make up the EdgePts object
     * @param tt the TimeTracker object used to track expensive operations. May be
     * null if tracking is not required.
     */
    EdgePts(CircularList<Point> lpt, TimeTracker tt) {
      this.mTimeTracker = tt;
        CircularList<Point>.CircListIter cli = lpt.circListIterator();
        Point ptPrev = null;
        Vec2 vPrev = null;
        int nRunLength = 0;
        /* for each point in the list */
        while (cli.hasNext() && snEdgePtsUsed < MAX_EDGEPTS-1) {
            Point pt = cli.next();
            Point ptNext = cli.getNext();
            /* get vector to next point */
            Vec2 v = pt.diff(ptNext);
            /* check for start of list */
            if (vPrev == null) {
              /* initialize bounding box */
                this.rectBounds = new JessRect(pt);
                /* initialize previous point, run length, and previous vector */
                ptPrev = pt;
                vPrev = v;
                nRunLength = 1;
            } else {
              /* extend bounding box */
                this.rectBounds.add(pt);
                /* check to see if we are continuing in the same direction */
                if (vPrev.equals(v)) {
                    nRunLength ++;
                } else {
                  /* new direction. Record chain code of previous direction
                   * and create EdgePt
                   */
                    byte bDir = getChainCode(vPrev);
                    EdgePt ep = srEdgePtStore[snEdgePtsUsed++];
                    ep.set(ptPrev, vPrev.times(nRunLength), bDir, nRunLength);
                    this.add(ep);
                    /* start new direction */
                    ptPrev = pt;
                    vPrev = v;
                    nRunLength = 1;
                }
            }
        }
        /* close circular list */
        byte bDir = getChainCode(vPrev);
        EdgePt ep = new EdgePt(ptPrev, vPrev.times(nRunLength), bDir,
                nRunLength);
        this.add(ep);
    }

    /**
     * Instantiates a new EdgePts object from an existing list of EdgePt objects.
     * Note that this code does not do runlength tracking or measure the chain
     * code. This is because this code is used when we are creating an EdgePts
     * object from an object which has already been subjected to polygonal
     * approximation, i.e., when we are splitting an EdgePts object into two parts.
     *
     * @param leps the list of EdgePt objects
     */
    EdgePts(List<EdgePt> leps) {
        EdgePt firstEdgePt = null;
        EdgePt prevEdgePt = null;
        for (EdgePt edgept : leps) {
            EdgePt ep = edgept;
            /* check for start of list */
            if (firstEdgePt == null) {
                firstEdgePt = ep;
            }
            /* initialize bounding box */
            if (this.rectBounds == null) {
                this.rectBounds = new JessRect(ep.getPos());
            } else {
                this.rectBounds.add(ep.getPos());
            }
            /* set vector from previous point to this one */
            if (prevEdgePt != null) {
                prevEdgePt.setVec(ep);
            }
            prevEdgePt = ep;
            this.add(ep);
        }
        // close circular list
        if (firstEdgePt != prevEdgePt) {
            prevEdgePt.setVec(firstEdgePt);
        }
    }
   
    /**
     * Count the number of fixed points in the EdgePts object. This is used
     * to determine when we have enough corners to consider a polygonal
     * approxmation done.
     *
     * @return the number of fixed points in the EdgePts object
     */
    private int countFixed() {
        int nCount = 0;
        for (EdgePt ep: this) {
            if (ep.getFixed()) {
                nCount ++;
            }
        }
        return nCount;
    }
   
    /**
     * splitDP implements the Douglas-Peucker algorithm to
     * recursively break a sequence of edge points into polygonal
     * segments by finding the point of maximum departure from a line linking
     * the first and last points and marking that point as fixed, then
     * applying splitDP to the sequence before and after that point,
     * until no point is more than a fixed distance from the line connecting
     * the beginning and end of the sequence.
     *
     * @param cliStart the starting point
     * @param cliEnd the ending point
     *
     * @return the number of new fixed points added
     */
    private int splitDP(CircListIter cliStart, CircListIter cliEnd) {
        // check for points only 1 point apart
        if (cliStart.getNext() == cliEnd.getPrevious()) {
            return 0;
        }
        // vecLine is the direction from the ending point to
        // the starting point
         Vec2 vecLine = cliEnd.getNext().getPos().diff(cliStart.getNext().getPos());
        // check for starting and ending points the same
        if (vecLine.getX() == 0 && vecLine.getY() == 0) {
          // measure from curve direction at start
            vecLine = cliStart.getPrevious().getVec();
        }
        // maxPerp is the max distance from the line to the current point
        int maxPerp = Integer.MIN_VALUE;
        CircListIter cli = cliStart.clone();
        cli.next();
        CircListIter cliMaxPoint = cli;
        this.mvSum.setXY(0, 0);
        // for each point from start to end
        while (cli.getNext() != cliEnd.getNext()) {
          // get the point
            this.mvSum.add(cli.next().getVec());
            // perp is the magnitude of the cross product
            // of vec (current position) x vecLine (line)
            // which is the distance of vec to the line
            // (times the length of vecLine which doesn't change)
            int perp = this.mvSum.crossMag(vecLine);
            perp *= perp;
            // if we're further from the line than we've
            // been before
            if (perp > maxPerp) {
                maxPerp = perp;
                cliMaxPoint = cli.clone();
                cliMaxPoint.previous();
            }
        };
        int vLen = 0;
        try {
          vLen = vecLine.length();
        } catch (jjil.core.Error er) {
         
        }
        // check to see if we should split again
        // remember maxPerp = | vLen | * (max perpendicular distance to vecLine)
        if (maxPerp >= PAR1 * vLen) {
          cliMaxPoint.getNext().setFixed();
            return 1 + splitDP(cliStart, cliMaxPoint) + splitDP(cliMaxPoint, cliEnd);
        } else {
          return 0;
        }
    };
   

    /**
     * Fix2.
     *
     * @param area the area
     */
    void fix2(int area) {
        CircularList<EdgePt>.CircListIter cliSearch;
        EdgePt ep = null;
        for (cliSearch = this.circListIterator(); cliSearch.hasNext();) {
            EdgePt prev = cliSearch.getPrevious();
            ep = cliSearch.next();
            EdgePt next = cliSearch.getNext();
            int dir1;
            if (!((((ep.getDir() - prev.getDir() + 1) & 7) < 3 &&
                    (dir1 = (prev.getDir() - next.getDir()) & 7) != 2 && dir1 != 6))) {
                break;
            }
        }

        boolean stopped = false;                   /*not finished yet */
        ep.setFixed(); /*fix it */
        CircularList<EdgePt>.CircListIter cli = new CircListIter(cliSearch);
        do {
            EdgePt linestartPrevPrev = cli.getPrevX3();
            EdgePt linestartPrev = cli.getPrevPrev();
            EdgePt linestart = ep;          /*possible start of line */
            EdgePt linestartNext = cli.getNext();
            int dir1 = ep.getDir();   /*first direction */
            /*length of dir1 */
            int sum1 = ep.getRunLength();
            EdgePt prev = ep;
            ep = cli.next();
            int dir2 = ep.getDir();   /*2nd direction */
            /*length in dir2 */
            int sum2 = ep.getRunLength();
            if (((dir1 - dir2 + 1) & 7) < 3) {
                while (prev.getDir() == cli.getNext().getDir()) {
                    prev = ep;
                    ep = cli.next();   /*look at next */
                    if (ep.getDir() == dir1) /*sum lengths */ {
                        sum1 += ep.getRunLength();
                    } else {
                        sum2 += ep.getRunLength();
                    }
                }

                if (!cli.hasNext()) {
                    stopped = true;             /*finished */

                }
                if (sum2 + sum1 > 2 && linestartPrev.getDir() == dir2 &&
                        (linestartPrev.getRunLength() >
                        linestart.getRunLength() || sum2 > sum1)) {
                    /*start is back one */
                    linestartNext = linestart;
                    linestart = linestartPrev;
                    linestartPrev = linestartPrevPrev;
                    linestart.setFixed();
                }

                if (((cli.getNext().getDir() - ep.getDir() + 1) & 7) >= 3 ||
                        (ep.getDir() == dir1 && sum1 >= sum2) ||
                        ((prev.getRunLength() < ep.getRunLength() ||
                        (ep.getDir() == dir2 && sum2 >= sum1)) &&
                        linestartNext != ep)) {
                    ep = cli.next();
                }
            }
            /*sharp bend */
            ep.setFixed();
        } while (cli.hasNext() && !stopped);
        /*do whole loop */
        cli = this.circListIterator();
        do {
            ep = cli.next();
            if (((ep.getRunLength() >= 8) &&
                    (ep.getDir() != 2) && (ep.getDir() != 6)) ||
                    ((ep.getRunLength() >= 8) &&
                    ((ep.getDir() == 2) || (ep.getDir() == 6)))) {
                ep.setFixed();
                EdgePt next = cli.getNext();
                next.setFixed();
            }
        } while (cli.hasNext());

        cli = this.circListIterator();
        do {
            EdgePt prevprev = cli.getPrevPrev();
            EdgePt prev = cli.getPrevious();
            ep = cli.next();
            EdgePt next = cli.getNext();
            EdgePt nextnext = cli.getNextNext();
            /*single fixed step */
            if (ep.getFixed() &&
                    ep.getRunLength() == 1 /*and neighours free */ && (next.getFixed() &&
                    !prev.getFixed()) /*same pair of dirs */ && (!cli.getNextNext().getFixed() &&
                    prev.getDir() == next.getDir() &&
                    prevprev.getDir() == nextnext.getDir()) && ((prev.getDir() - ep.getDir() + 1) & 7) < 3) {
                /*unfix it */
                ep.resetFixed();
                next.resetFixed();
            }
        } while (cli.hasNext());       /*until finished */

        stopped = false;
        if (area < 450) {
            area = 450;
        }
        int gapmin = area * FIXED_DIST * FIXED_DIST / 44000;

        cli = this.circListIterator();
        int fixed_count = 0;
        do {
            ep = cli.next();
            if (ep.getFixed()) {
                fixed_count++;
            }
        } while (cli.hasNext());
       
        cli = this.circListIterator();
       
        do {
            ep = cli.next();
        } while (!ep.getFixed());

        EdgePt edgefix0 = ep;

        do {
            ep = cli.next();
        } while (!ep.getFixed());

        EdgePt edgefix1 = ep;
        do {
            ep = cli.next();
        } while (!ep.getFixed());

        EdgePt edgefix2 = ep;

        do {
            ep = cli.next();
        } while (!ep.getFixed());

        EdgePt edgefix3 = ep;

        EdgePt startfix = edgefix2;

        do {
            if (fixed_count <= 3) {
                break;                     //already too few

            }
            Vec2 d12vec = edgefix1.getPos().diff(edgefix2.getPos());
            int d12 = d12vec.lengthSqr();
            if (d12 <= gapmin) {
                Vec2 d01vec = edgefix0.getPos().diff(edgefix1.getPos());
                int d01 = d01vec.lengthSqr();
                Vec2 d23vec = edgefix2.getPos().diff(edgefix3.getPos());
                int d23 = d23vec.lengthSqr();
                if (d01 > d23) {
                    edgefix2.resetFixed();
                    fixed_count--;
                } else {
                    edgefix1.resetFixed();
                    fixed_count--;
                    edgefix1 = edgefix2;
                }
            } else {
                edgefix0 = edgefix1;
                edgefix1 = edgefix2;
            }
            edgefix2 = edgefix3;
            ep = cli.next();
            while (!ep.getFixed()) {
                if (ep == startfix) {
                    stopped = true;
                }
                ep = cli.next();
            }
            edgefix3 = ep;
        } while ((edgefix2 != startfix) && (!stopped));

    }

    /** sbLookup translates the offset to the next point to the chain code. The offset is represented as ((x+1)*3+(y+1)) where -1<=x<=1 and -1<=y<=1 are the differences between the current point and the next point. So the chain codes and offsets around the current point are  y         offset                    chain code ^         6  7  8                   5  6  7 |         3  4  5                   4 -1  0  (-1 is unused) .->x      0  1  2                   3  2  1 */
    static byte sbLookup[] = new byte[]{
        3, 2, 1, 4, -1, 0, 5, 6, 7
    };
   
    /**
     * Gets the chain code.
     *
     * @param v the vector to the next point:
     * (x,y) where -1<=x<=1 and -1<=y<=1, (x,y) != (0,0)
     *
     * @return the chain code -- byte 0-8, clockwise around
     * central point, starting at 0 to right
     */
    private byte getChainCode(Vec2 v) {
        assert Math.abs(v.getX()) <= 1 &&
                Math.abs(v.getY()) <= 1;
        int nIndex = (v.getX()+1)*3 + (v.getY()+1);
        return sbLookup[nIndex];
    }
   
    /**
     * Gets the EdgePt objects which have not yet been traversed, starting
     * at the given iterator. The purpose of this is to pull out closed loops
     * of EdgePt objects from an existing EdgePts object when splitting.
     *
     * @param lcli the iterator marking the starting point
     *
     * @return the edge pts, up to the first traversed point. EdgePts is modified
     * by marking points as traversed as we add them to the resulting list.
     */
    private List<EdgePts> getEdgePts(List<EdgePts.CircListIter> lcli) {
        List<EdgePts> lcp = new ArrayList<EdgePts>();
        for (CircularList<EdgePt>.CircListIter cli: lcli) {
            EdgePt ep = cli.getNext();
            if (!ep.isTraversed()) {
                EdgePts eps = new EdgePts(this.mTimeTracker);
                while (cli.hasNext() && !cli.getNext().isTraversed()) {
                    ep = cli.next();
                    ep.setTraversed();
                    eps.add(ep);
                }
                lcp.add(eps);
            } else {
                break;
            }
        }
        return lcp;
    }
   
    /**
     * We have a situation like this
     * <code>
     * cliPrev.getNext() -> cliPrev.getNextNext()
     *
     * cliNext.getNextNext() <= cliNext.getNext()
     * we exchange iterators and add points so we get this
     * cliPrev --------> ptPrev      |---> cliPrev.getNext()
     * |         |
     * cliNext.getNext <---|         |<--- cliNext
     * </code>
     * This is for splitting characters which are joined together.
     *
     * @param cliPrev "top" iterator
     * @param cliNext "bottom" iterator
     * @param ptPrev intermediate point for top
     * @param ptNext intermediate point for bottom
     */
    private void insertAndSplit(CircListIter cliPrev, CircListIter cliNext,
            Point ptPrev, Point ptNext) {
        cliPrev.exchange(cliNext);
        EdgePt ep = new EdgePt(ptPrev, cliNext.getNext().getPos().diff(ptPrev));
        cliPrev.getNext().setVec(ep);
        this.add(cliPrev, ep);
        ep = new EdgePt(ptNext, cliPrev.getNext().getPos().diff(ptNext));
        cliNext.getNext().setVec(ep);
        this.add(cliNext, ep);
    }

    /**
     * Poly2.
     */
    void poly2() {
        boolean bFound = false;
        CircListIter cli = this.circListIterator();
        while (cli.hasNext()) {
            if (cli.getNext().getFixed() &&
                    !cli.getNextNext().getFixed()) {
                bFound = true;
                break;
            }
            cli.next();
        }
        if (!bFound) {
            cli = this.circListIterator();
            if (!cli.getNext().getFixed()) {
                cli.getNext().setFixed();
                bFound = true;
            }
        }
        if (bFound) {
            CircListIter loopstart = cli.clone();
            int nFixed = countFixed();
            while (nFixed < 3) {
                cli = new CircListIter(loopstart);
                do {
                    CircListIter linestart = cli.clone();
                    do {
                        cli.next();
                    } while (!cli.getNext().getFixed() &&
                            cli.hasNext());
                    nFixed += splitDP(linestart, cli);

                    while (cli.getNextNext().getFixed() &&
                            cli.hasNext()) {  /*look for next non-fixed */
                        cli.next();
                    }
                } while (cli.hasNext());
            };
            cli = this.circListIterator();
            // remove all the non-fixed points from the loop
            do {
                if (!cli.next().getFixed()) {
                    cli.remove();
                    cli.getPrevious().setVec(cli.getNext());
                };
            } while (cli.hasNext());
        }
    }
   
    /**
     * Reset traversed.
     */
    void resetTraversed() {
        for (EdgePt ep: this) {
            ep.resetTraversed();
        }
    }
   
    /**
     * Split.
     *
     * @param nX the n x
     *
     * @return the pair< list< edge pts>, list< edge pts>>
     */
    Pair<List<EdgePts>, List<EdgePts>> split(int nX) {
      if (this.mTimeTracker != null) {
        this.mTimeTracker.startTask("EdgePts.split");
      }
        ArrayList<Pair<SortableCircListIter,SortableCircListIter>> sep =
                new ArrayList<Pair<SortableCircListIter,SortableCircListIter>>();
        boolean mbLeft = true;
        SortableCircListIter scliLast = null;
        for (CircularList<EdgePt>.CircListIter cli = this.circListIterator();
            cli.hasNext();) {
            EdgePt epThis = cli.getNext();
            EdgePt epNext = cli.getNextNext();
            // see if this edge crosses the split point
            Point ptThis = epThis.getPos();
            Point ptNext = epNext.getPos();
            if (ptThis.getX() >= nX) {
                mbLeft = false;
            }
            if ((ptThis.getX() < nX) != (ptNext.getX() < nX)) {
                int nY = ptThis.getY() + (ptNext.getY() - ptThis.getY()) *
                        (nX - ptThis.getX()) / (ptNext.getX() - ptThis.getX());
                SortableCircListIter scliThis =
                        new SortableCircListIter(nY, cli.clone());
                if (scliLast == null) {
                    scliLast = scliThis;
                } else {
                    sep.add(new Pair<SortableCircListIter,SortableCircListIter>
                      (scliLast, scliThis));
                    scliLast = null;
                }
            }
            cli.next();
        }
        assert scliLast == null;
        if (sep.size() == 0) {
            ArrayList<EdgePts> al = new ArrayList<EdgePts>();
            al.add(this);
            if (mbLeft) {
                return new Pair<List<EdgePts>, List<EdgePts>>(al, null);
            } else {
                return new Pair<List<EdgePts>, List<EdgePts>>(null, al);
            }
        }
        this.resetTraversed();
        ArrayList<CircularList<EdgePt>.CircListIter> alcliLeft =
                new ArrayList<CircularList<EdgePt>.CircListIter>(),
                alcliRight = new ArrayList<CircularList<EdgePt>.CircListIter>();
        for (Pair<SortableCircListIter,SortableCircListIter> prscli:
                sep) {
            SortableCircListIter sepTop = prscli.getFirst();
            SortableCircListIter sepBottom = prscli.getSecond();
            EdgePts.CircListIter cliTop = sepTop.getCircListIter();
            EdgePts.CircListIter cliBottom = sepBottom.getCircListIter();
            if (cliTop.getNext().getPos().getX() < cliBottom.getNext().getPos().getX()) {
                Point ptTop = new Point(nX-1, sepTop.getY());
                Point ptBottom = new Point(nX, sepBottom.getY());
                insertAndSplit(cliTop, cliBottom, ptTop, ptBottom);
                alcliLeft.add(new CircListIter(cliTop));
                alcliRight.add(new CircListIter(cliBottom));               
            } else {
                Point ptTop = new Point(nX, sepTop.getY());
                Point ptBottom = new Point(nX-1, sepBottom.getY());
                insertAndSplit(cliTop, cliBottom, ptTop, ptBottom);
                alcliRight.add(new CircListIter(cliTop));
                alcliLeft.add(new CircListIter(cliBottom));               
               
            }
        }
        List<EdgePts> lcpLeft = getEdgePts(alcliLeft),
                lcpRight = getEdgePts(alcliRight);
        assert lcpLeft.size() > 0 && lcpRight.size() > 0;
      if (this.mTimeTracker != null) {
        this.mTimeTracker.endTask("EdgePts.split");
      }
        return new Pair<List<EdgePts>,List<EdgePts>>(lcpLeft, lcpRight);
    }
   
    /**
     * Reset.
     */
    public static void reset() {
      snEdgePtsUsed = 0;
    }
}
TOP

Related Classes of jjil.j2se.ocr.EdgePts

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.