org.stathissideris.ascii2image.text.CellSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plantuml-lgpl Show documentation
Show all versions of plantuml-lgpl Show documentation
PlantUML is a component that allows to quickly write diagrams from text.
// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
* |
* | PlantUML : a free UML diagram generator
* |
* +=======================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/liberapay (only 1€ per month!)
* https://plantuml.com/paypal
*
*
* PlantUML 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.
*
* PlantUML 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 GNU Lesser General Public License
* along with this library. If not, see .
*
* PlantUML can occasionally display sponsored or advertising messages. Those
* messages are usually generated on welcome or error images and never on
* functional diagrams.
* See https://plantuml.com/professional if you want to remove them
*
* Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
* are owned by the author of their corresponding sources code (that is, their
* textual description in PlantUML language). Those images are not covered by
* this LGPL license.
*
* The generated images can then be used without any reference to the LGPL license.
* It is not even necessary to stipulate that they have been generated with PlantUML,
* although this will be appreciated by the PlantUML team.
*
* There is an exception : if the textual description in PlantUML language is also covered
* by any license, then the generated images are logically covered
* by the very same license.
*
* This is the IGY distribution (Install GraphViz by Yourself).
* You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
* (see https://plantuml.com/graphviz-dot )
*
* Icons provided by OpenIconic : https://useiconic.com/open
* Archimate sprites provided by Archi : http://www.archimatetool.com
* Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
* Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
* ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
* ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
* CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
* Brotli (c) by the Brotli Authors https://github.com/google/brotli
* Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
* Twemoji (c) by Twitter at https://twemoji.twitter.com/
*
*/
package org.stathissideris.ascii2image.text;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
*
* @author Efstathios Sideris
*/
public class CellSet implements Iterable {
private static final boolean DEBUG = false;
private static final boolean VERBOSE_DEBUG = false;
public static final int TYPE_CLOSED = 0;
public static final int TYPE_OPEN = 1;
public static final int TYPE_MIXED = 2;
public static final int TYPE_HAS_CLOSED_AREA = 3;
public static final int TYPE_UNDETERMINED = 4;
Set internalSet = new HashSet();
private int type = TYPE_UNDETERMINED;
private boolean typeIsValid = false;
private static final Object FAKE = new Object();
public CellSet(){
}
public CellSet(CellSet other){
addAll(other);
}
public Iterator iterator(){
return internalSet.iterator();
}
public Object add(TextGrid.Cell cell){
return internalSet.add(cell);
}
public void addAll(CellSet set){
internalSet.addAll(set.internalSet);
}
void clear(){
internalSet.clear();
}
public int size() {
return internalSet.size();
}
public TextGrid.Cell getFirst(){
//return internalSet.get(0);
return (TextGrid.Cell) internalSet.iterator().next();
}
public void printAsGrid(){
TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2);
grid.fillCellsWith(this, '*');
grid.printDebug();
}
public void printDebug(){
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = it.next();
System.out.print(cell);
if(it.hasNext()) System.out.print(" ");
}
System.out.println();
}
public String getCellsAsString(){
StringBuilder str = new StringBuilder();
Iterator it = iterator();
while(it.hasNext()){
str.append(it.next().toString());
if(it.hasNext()) str.append("/");
}
return str.toString();
}
public String toString(){
TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2);
grid.fillCellsWith(this, '*');
return grid.getDebugString();
}
/**
* Deep copy
*
* @param set
* @return
*/
public static CellSet copyCellSet(CellSet set) {
TextGrid grid = new TextGrid();
CellSet newSet = new CellSet();
Iterator it = set.iterator();
while (it.hasNext()) {
TextGrid.Cell cell = (TextGrid.Cell) it.next();
TextGrid.Cell newCell = grid.new Cell(cell);
newSet.add(newCell);
}
return newSet;
}
/*public BoundarySet(BoundarySet set) {
Iterator it = set.iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
add(new TextGrid.Cell(cell));
}
}*/
public int getType(TextGrid grid) {
if(typeIsValid) return type;
typeIsValid = true;
if(size() == 1) {
type = TYPE_OPEN;
return TYPE_OPEN;
}
int typeTrace = getTypeAccordingToTraceMethod(grid);
if(DEBUG){
System.out.println("trace: "+typeTrace);
}
if(typeTrace == TYPE_OPEN) {
type = TYPE_OPEN;
return TYPE_OPEN;
}
if(typeTrace == TYPE_CLOSED) {
type = TYPE_CLOSED;
return TYPE_CLOSED;
}
if(typeTrace == TYPE_UNDETERMINED) {
int typeFill = getTypeAccordingToFillMethod(grid);
if(typeFill == TYPE_HAS_CLOSED_AREA){
type = TYPE_MIXED;
return TYPE_MIXED;
} else if(typeFill == TYPE_OPEN){
type = TYPE_OPEN;
return TYPE_OPEN;
}
}
//in the case that both return undetermined:
type = TYPE_UNDETERMINED;
return TYPE_UNDETERMINED;
}
private int getTypeAccordingToTraceMethod(TextGrid grid) {
if(size() < 2) return TYPE_OPEN;
TextGrid workGrid = TextGrid.makeSameSizeAs(grid);
grid.copyCellsTo(this, workGrid);
//start with a line end if it exists or with a "random" cell if not
TextGrid.Cell start = null;
for(TextGrid.Cell cell : this)
if(workGrid.isLinesEnd(cell))
start = cell;
if(start == null) start = (TextGrid.Cell) getFirst();
if (DEBUG)
System.out.println("Tracing:\nStarting at "+start+" ("+grid.getCellTypeAsString(start)+")");
TextGrid.Cell previous = start;
TextGrid.Cell cell = null;
CellSet nextCells = workGrid.followCell(previous);
if(nextCells.size() == 0) return TYPE_OPEN;
cell = (TextGrid.Cell) nextCells.getFirst();
if (DEBUG)
System.out.println("\tat cell "+cell+" ("+grid.getCellTypeAsString(cell)+")");
while(!cell.equals(start)){
nextCells = workGrid.followCell(cell, previous);
if(nextCells.size() == 0) {
if (DEBUG)
System.out.println("-> Found dead-end, shape is open");
return TYPE_OPEN;
} if(nextCells.size() == 1) {
previous = cell;
cell = (TextGrid.Cell) nextCells.getFirst();
if (DEBUG)
System.out.println("\tat cell "+cell+" ("+grid.getCellTypeAsString(cell)+")");
} else if(nextCells.size() > 1) {
if (DEBUG)
System.out.println("-> Found intersection at cell "+cell);
return TYPE_UNDETERMINED;
}
}
if (DEBUG)
System.out.println("-> Arrived back to start, shape is closed");
return TYPE_CLOSED;
// boolean hasMoved = false;
//
// CellSet workSet;
// workSet = new CellSet(this);
//
// TextGrid.Cell start = (TextGrid.Cell) get(0);
//
// workSet.remove(start);
// TextGrid.Cell cell = workSet.findCellNextTo(start);
//
// while(true && cell != null){
//
// hasMoved = true;
// workSet.remove(cell);
//
// CellSet setOfNeighbours = workSet.findCellsNextTo(cell);
//
// if(setOfNeighbours.isEmpty()) break;
//
// TextGrid.Cell c = null;
// if(setOfNeighbours.size() == 1) c = (TextGrid.Cell) setOfNeighbours.get(0);
// if(setOfNeighbours.size() > 1) return TYPE_UNDETERMINED;
// if(c == null) break;
// else cell = c;
// }
// if(cell != null && start.isNextTo(cell) && hasMoved) return TYPE_CLOSED;
// else return TYPE_OPEN;
}
private int getTypeAccordingToFillMethod(TextGrid grid){
if(size() == 0) return TYPE_OPEN;
CellSet tempSet = copyCellSet(this);
tempSet.translate( -this.getMinX() + 1, -this.getMinY() + 1);
TextGrid subGrid = grid.getSubGrid(getMinX() - 1, getMinY() - 1, getWidth() + 3, getHeight() + 3);
AbstractionGrid abstraction = new AbstractionGrid(subGrid, tempSet);
TextGrid temp = abstraction.getCopyOfInternalBuffer();
TextGrid.Cell cell1 = null;
TextGrid.Cell cell2 = null;
int width = temp.getWidth();
int height = temp.getHeight();
TextGrid.Cell fillCell = null;
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
TextGrid.Cell cCell = temp.new Cell(x, y);
if(temp.isBlank(cCell)){
fillCell = cCell;
break;
}
}
}
if(fillCell == null){
System.err.println("Unexpected error: fill method cannot fill anywhere");
return TYPE_UNDETERMINED;
}
temp.fillContinuousArea(fillCell, '*');
if(VERBOSE_DEBUG) {System.out.println("Buffer after filling:"); temp.printDebug();}
if(temp.hasBlankCells()) return TYPE_HAS_CLOSED_AREA;
else return TYPE_OPEN;
}
public void translate(int dx, int dy){
typeIsValid = false;
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cCell = (TextGrid.Cell) it.next();
cCell.x += dx;
cCell.y += dy;
}
}
public TextGrid.Cell find(TextGrid.Cell cell){
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cCell = it.next();
if(cCell.equals(cell)) return cCell;
}
return null;
}
public boolean contains(TextGrid.Cell cell){
if(cell == null) return false;
return internalSet.contains(cell);
}
// public boolean contains(TextGrid.Cell cell){
// Iterator it = iterator();
// while(it.hasNext()){
// TextGrid.Cell cCell = it.next();
// if(cCell.equals(cell)) return true;
// }
// return false;
// }
public void addSet(CellSet set){
typeIsValid = false;
this.addAll(set);
}
public boolean hasCommonCells(CellSet otherSet){
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = it.next();
if(otherSet.contains(cell)) return true;
}
return false;
}
public TextGrid.Cell find(int x, int y){
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cCell = (TextGrid.Cell) it.next();
if(cCell.x == x && cCell.y == y) return cCell;
}
return null;
}
public CellSet getFilledEquivalent(TextGrid textGrid){
if(this.getType(textGrid) == CellSet.TYPE_OPEN) return new CellSet(this);
TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2);
grid.fillCellsWith(this, '*');
//find a cell that has a blank both on the east and the west
TextGrid.Cell cell = null;
boolean finished = false;
for(int y = 0; y < grid.getHeight() && !finished; y++){
for(int x = 0; x < grid.getWidth() && !finished; x++){
cell = grid.new Cell(x, y);
if(!grid.isBlank(cell)
&& grid.isBlank(cell.getEast())
&& grid.isBlank(cell.getWest())){
finished = true;
}
}
}
if(cell != null){
cell = cell.getEast();
if(grid.isOutOfBounds(cell)) return new CellSet(this);
grid.fillContinuousArea(cell, '*');
return grid.getAllNonBlank();
}
System.err.println("Unexpected error, cannot find the filled equivalent of CellSet");
return null;
}
/**
* Returns the first cell that is found to be next to cell
.
*
* @param cell
* @return
*/
public TextGrid.Cell findCellNextTo(TextGrid.Cell cell){
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cCell = (TextGrid.Cell) it.next();
if(cCell.isNextTo(cell)) return cCell;
}
return null;
}
/**
* Returns all the cells that are found to be next to cell
.
*
* @param cell
* @return
*/
public CellSet findCellsNextTo(TextGrid.Cell cell){
if(cell == null) throw new IllegalArgumentException("cell cannot be null");
CellSet set = new CellSet();
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cCell = (TextGrid.Cell) it.next();
if(cCell.isNextTo(cell)) set.add(cCell);
}
return set;
}
public void appendSet(CellSet set){
typeIsValid = false;
Iterator it = set.iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
if(find(cell) == null) add(cell);
}
}
public void subtractSet(CellSet set){
typeIsValid = false;
Iterator it = set.iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
TextGrid.Cell thisCell = find(cell);
if(thisCell != null) remove(thisCell);
}
}
public int getWidth(){
return getMaxX() - getMinX();
}
public int getHeight(){
return getMaxY() - getMinY();
}
public int getMaxX(){
int result = 0;
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
if(cell.x > result) result = cell.x;
}
return result;
}
public int getMinX(){
int result = Integer.MAX_VALUE;
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
if(cell.x < result) result = cell.x;
}
return result;
}
public int getMaxY(){
int result = 0;
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
if(cell.y > result) result = cell.y;
}
return result;
}
public int getMinY(){
int result = Integer.MAX_VALUE;
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
if(cell.y < result) result = cell.y;
}
return result;
}
public Object remove(TextGrid.Cell cell){
typeIsValid = false;
cell = find(cell);
if(cell != null) return internalSet.remove(cell);
else return null;
}
public boolean equals(Object o){
CellSet otherSet = (CellSet) o;
return internalSet.equals(otherSet.internalSet);
}
public static ArrayList removeDuplicateSets(ArrayList list) {
ArrayList uniqueSets = new ArrayList();
Iterator it = list.iterator();
while(it.hasNext()){
CellSet set = (CellSet) it.next();
boolean isOriginal = true;
Iterator uniquesIt = uniqueSets.iterator();
while(uniquesIt.hasNext()){
CellSet uniqueSet = (CellSet) uniquesIt.next();
if(set.equals(uniqueSet)){
isOriginal = false;
}
}
if(isOriginal) uniqueSets.add(set);
}
return uniqueSets;
}
/**
* Takes into account character info from the grid
*
* @return ArrayList of distinct BoundarySetS
*/
public ArrayList breakIntoDistinctBoundaries(TextGrid grid){
ArrayList result;
AbstractionGrid temp = new AbstractionGrid(grid, this);
result = temp.getDistinctShapes();
return result;
}
/**
*
* @return ArrayList of distinct BoundarySetS
*/
public ArrayList breakIntoDistinctBoundaries(){
ArrayList result = new ArrayList();
//CellSet tempSet = copyCellSet(this);
//tempSet.translate( - this.getMinX() + 1, - this.getMinY() + 1);
// TextGrid boundaryGrid = new TextGrid(tempSet.getMaxX()+2, tempSet.getMaxY()+2);
// boundaryGrid.fillCellsWith(tempSet, '*');
TextGrid boundaryGrid = new TextGrid(getMaxX()+2, getMaxY()+2);
boundaryGrid.fillCellsWith(this, '*');
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell cell = (TextGrid.Cell) it.next();
if(boundaryGrid.isBlank(cell.x, cell.y)) continue;
CellSet boundarySet = boundaryGrid.fillContinuousArea(cell.x, cell.y, ' ');
//boundarySet.translate( this.getMinX() - 1, this.getMinY() - 1);
result.add(boundarySet);
}
return result;
}
/**
*
* Breaks that:
*
* +-----+
* | |
* + ---+-------------------
* | |
* +-----+
*
*
* into the following 3:
*
*
* +-----+
* | |
* + +
* | |
* +-----+
*
* ---
* -------------------
*
*
* @param grid
* @return a list of boundaries that are either open or closed but not mixed
* and they are equivalent to the this
*/
public ArrayList breakTrulyMixedBoundaries(TextGrid grid){
ArrayList result = new ArrayList();
CellSet visitedEnds = new CellSet();
TextGrid workGrid = TextGrid.makeSameSizeAs(grid);
grid.copyCellsTo(this, workGrid);
if (DEBUG){
System.out.println("Breaking truly mixed boundaries below:");
workGrid.printDebug();
}
Iterator it = iterator();
while(it.hasNext()){
TextGrid.Cell start = (TextGrid.Cell) it.next();
if(workGrid.isLinesEnd(start) && !visitedEnds.contains(start)){
if (DEBUG)
System.out.println("Starting new subshape:");
CellSet set = new CellSet();
set.add(start);
if(DEBUG) System.out.println("Added boundary "+start);
TextGrid.Cell previous = start;
TextGrid.Cell cell = null;
CellSet nextCells = workGrid.followCell(previous);
if(nextCells.size() == 0)
throw new IllegalArgumentException("This shape is either open but multipart or has only one cell, and cannot be processed by this method");
cell = (TextGrid.Cell) nextCells.getFirst();
set.add(cell);
if(DEBUG) System.out.println("Added boundary "+cell);
boolean finished = false;
if(workGrid.isLinesEnd(cell)){
visitedEnds.add(cell);
finished = true;
}
while(!finished){
nextCells = workGrid.followCell(cell, previous);
if(nextCells.size() == 1) {
set.add(cell);
if(DEBUG) System.out.println("Added boundary " + cell);
previous = cell;
cell = (TextGrid.Cell) nextCells.getFirst();
//if(!cell.equals(start) && grid.isPointCell(cell))
// s.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound));
if(workGrid.isLinesEnd(cell)){
visitedEnds.add(cell);
finished = true;
}
} else if(nextCells.size() > 1) {
finished = true;
}
}
result.add(set);
}
}
//substract all boundary sets from this CellSet
CellSet whatsLeft = new CellSet(this);
it = result.iterator();
while (it.hasNext()) {
CellSet set = (CellSet) it.next();
whatsLeft.subtractSet(set);
if(DEBUG) set.printAsGrid();
}
result.add(whatsLeft);
if(DEBUG) whatsLeft.printAsGrid();
return result;
}
public TextGrid makeIntoGrid(){
TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2);
grid.fillCellsWith(this, '*');
return grid;
}
public CellSet makeScaledOneThirdEquivalent(){
TextGrid gridBig = this.makeIntoGrid();
gridBig.fillCellsWith(this, '*');
if (VERBOSE_DEBUG){
System.out.println("---> making ScaledOneThirdEquivalent of:");
gridBig.printDebug();
}
TextGrid gridSmall = new TextGrid((getMaxX() + 2) / 3, (getMaxY() + 2) / 3);
for(int y = 0; y < gridBig.getHeight(); y++){
for(int x = 0; x < gridBig.getWidth(); x++){
TextGrid.Cell cell = gridBig.new Cell(x, y);
if(!gridBig.isBlank(cell)) gridSmall.set(x/3, y/3, '*');
}
}
if (VERBOSE_DEBUG){
System.out.println("---> made into grid:");
gridSmall.printDebug();
}
return gridSmall.getAllNonBlank();
}
}