/*
* Copyright 2011 Uwe Krueger.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mandelsoft.mand.tools;
import com.mandelsoft.mand.Environment;
import com.mandelsoft.mand.IllegalConfigurationException;
import com.mandelsoft.mand.MandIter;
import com.mandelsoft.mand.MandelData;
import com.mandelsoft.mand.MandelException;
import com.mandelsoft.io.AbstractFile;
import com.mandelsoft.mand.MandelInfo;
import com.mandelsoft.mand.MandelName;
import com.mandelsoft.mand.PixelIterator;
import com.mandelsoft.mand.cm.ColormapModel.ResizeMode;
import com.mandelsoft.mand.scan.MandelFolder;
import com.mandelsoft.mand.scan.MandelScanner;
import com.mandelsoft.mand.util.MandUtils;
import com.mandelsoft.io.FolderLock;
import com.mandelsoft.mand.QualifiedMandelName;
import com.mandelsoft.mand.scan.MandelHandle;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
*
* @author Uwe Krueger
*/
public class Mand extends Command {
public static class ShutdownException extends RuntimeException {
}
static public final double BOUND=10;
private File file;
private File save;
private QualifiedMandelName name;
private MandelData md;
private MandelInfo mi;
private PixelIterator pi;
private int limit;
private int[][] raster;
private Filter filter;
public Mand(MandelData md, QualifiedMandelName n)
{
this.md=md;
this.name=n;
this.mi=md.getInfo();
this.limit=mi.getLimitIt();
}
public Mand(MandelData md, QualifiedMandelName n, Environment env)
throws IOException
{
this(md,n);
this.file=env.mapToRasterFile(md.getFile());
this.save=env.mapToIncompleteFile(md.getFile());
}
public Mand(File f, Environment env) throws IOException
{
this(new MandelData(f),QualifiedMandelName.create(f), env);
}
public Mand(MandelData md, MandelData old, QualifiedMandelName n,
Environment env) throws IOException
{
this(md, n, env);
if (old!=null && old.getFile().isFile()) {
md.setRaster(old.getRaster());
md.setMapper(ResizeMode.RESIZE_LOCK_COLORS, old.getMapper());
md.getInfo().setSite(old.getInfo().getSite());
md.getInfo().setCreator(old.getInfo().getCreator());
md.getInfo().setLocation(old.getInfo().getLocation());
md.getInfo().setName(old.getInfo().getName());
md.getInfo().setTime(old.getInfo().getTime());
if (!old.isIncomplete()) { // keep old file name
this.file=old.getFile().getFile();
}
}
}
void setFilter(Filter filter)
{
this.filter=filter;
}
public File getFile()
{
return file;
}
public boolean isAborted()
{
return aborted;
}
public void check() throws IOException
{
int rx=md.getRaster().getRX();
int ry=md.getRaster().getRY();
MandelData tmp=new MandelData(file);
if (rx!=tmp.getRaster().getRX()) {
throw new MandelException("rx mismatch: "+
tmp.getRaster().getRX()+"!="+
rx);
}
if (ry!=tmp.getRaster().getRY()) {
throw new MandelException("ry mismatch: "+
tmp.getRaster().getRY()+"!="+
ry);
}
int[][] tmpraster=tmp.getRaster().getRaster();
for (int y=0; y<ry; y++) {
for (int x=0; x<rx; x++) {
if (raster[y][x]!=tmpraster[y][x])
throw new MandelException("content mismatch");
}
}
}
public boolean calculate()
{
setupContext();
if (filter!=null) {
if (!filter.filter(name, pi)) return false;
}
System.out.println((md.getRaster()==null?"":"re")+
"calculating "+(file==null?"":file)+"... ("+name+")");
if (md.getRaster()!=null&&md.getInfo().getMaxIt()>limit) {
System.out.println("nothing to be done");
return true;
}
raster=md.createRaster().getRaster();
long start=System.currentTimeMillis();
try {
calc2();
}
catch (ShutdownException ex) {
aborted=true;
}
finally {
long end=System.currentTimeMillis();
mi.setTime(mi.getTime()+(int)((end-start)/1000));
mi.setRasterCreationTime(end);
saveContext();
}
return true;
}
////////////////////////////////////////////////////////////////////////
// calculation
////////////////////////////////////////////////////////////////////////
private int rx;
private int ry;
private int min;
private int max;
private long cnt;
private int mccnt;
private int mcnt;
private void setupContext()
{
pi=MandIter.createPixelIterator(mi);
rx=mi.getRX();
ry=mi.getRY();
min=mi.getMinIt();
if (min==0) min=limit; // not yet calculated
max=mi.getMaxIt();
cnt=0;
mccnt=0;
mcnt=0;
}
private void saveContext()
{
mi.setMinIt(min);
mi.setMaxIt(max);
mi.setNumIt(cnt);
mi.setMCnt(mcnt);
mi.setMCCnt(mccnt);
}
private void calc1()
{
for (int y=0; y<ry; y++) {
pi.setY(y);
for (int x=0; x<rx; x++) {
pi.setX(x);
handle(x,y);
}
}
}
private boolean aborted;
private long lastcheck=0;
static private final long TIMEOUT = 1000 * 60 * 10; // 10 min
static private final File shutdown = new File("shutdown");
int handle(int x, int y)
{ int it=raster[y][x];
if (it==0) {
int i=pi.iter();
if (i>limit) {
raster[y][x]=it=0;
mccnt++;
mcnt++;
i--;
}
else {
raster[y][x]=it=i;
}
if (i<min) min=i;
if (i>max) max=i;
cnt+=i;
}
else cnt+=it;
long cur=System.currentTimeMillis();
if (cur>lastcheck+TIMEOUT) {
lastcheck=cur;
if (shutdown.exists()) throw new ShutdownException();
}
return it;
}
private void calc2()
{ int u;
calcHLine(0,0,rx);
calcHLine(0,ry-1,rx);
calcVLine(0,1,ry-2);
u=calcVLine(rx-1,1,ry-2);
calcBox(u, 0,0,rx,ry);
}
private int calcHLine(int sx, int sy, int n)
{
pi.setX(sx);
pi.setY(sy);
int u=handle(sx,sy);
for (int x=sx+1; x<sx+n; x++) {
pi.setX(x);
int it=handle(x,sy);
if (it!=u) u=-1;
}
return u;
}
private int calcVLine(int sx, int sy, int n)
{
pi.setX(sx);
pi.setY(sy);
int u=handle(sx,sy);
for (int y=sy+1; y<sy+n; y++) {
pi.setY(y);
int it=handle(sx,y);
if (it!=u) u=-1;
}
return u;
}
private void calcBox(int u, int sx, int sy, int nx, int ny)
{
//System.out.println("calcBox "+sx+","+sy+"("+nx+"x"+ny+")");
if (nx<=2 || ny<=2) return;
if (u>=0) {
u=checkHLine(u, sx, sy, nx);
u=checkHLine(u, sx, sy+ny-1, nx);
u=checkVLine(u, sx, sy+1, ny-2);
u=checkVLine(u, sx+nx-1, sy+1, ny-2);
if (u>=0) {
fillBox(sx+1,sy+1,nx-2,ny-2,u);
return;
}
}
if (nx>ny) {
// divide horizontally
int s=(nx-1)/2;
if (s!=0) {
//System.out.println("s="+s);
u=calcVLine(sx+s,sy+1,ny-2);
calcBox(u,sx,sy,s+1,ny);
calcBox(u,sx+s,sy,nx-s,ny);
}
}
else {
// divide vertically
int s=(ny-1)/2;
if (s!=0) {
u=calcHLine(sx+1,sy+s,nx-2);
calcBox(u,sx,sy,nx,s+1);
calcBox(u,sx,sy+s,nx,ny-s);
}
}
}
private int checkHLine(int u, int sx, int sy, int n)
{
if (u>=0) for (int x=sx; x<sx+n; x++) {
if (raster[sy][x]!=u) return -1;
}
return u;
}
private int checkVLine(int u, int sx, int sy, int n)
{
if (u>=0) for (int y=sy; y<sy+n; y++) {
if (raster[y][sx]!=u) return -1;
}
return u;
}
private void fillBox(int sx, int sy, int nx, int ny, int u)
{
//System.out.println("fill "+sx+","+sy+"("+nx+"x"+ny+") with "+u);
for (int y=sy; y<sy+ny; y++) {
for (int x=sx; x<sx+nx; x++) {
raster[y][x]=u;
}
}
if (u==0) mcnt+=nx*ny;
}
////////////////////////////////////////////////////////////////////////
// iteration for pixel
////////////////////////////////////////////////////////////////////////
private int iter(double x, double y, double px, double py)
{
double x2=x*x;
double y2=y*y;
int it=0;
while (x2+y2<BOUND&&++it<=limit) {
double xn=x2-y2+px;
double yn=2*x*y+py;
x=xn;
x2=x*x;
y=yn;
y2=y*y;
}
return it;
}
public void write() throws IOException
{
write(true);
}
public void write(boolean verbose) throws IOException
{
if (file==null) throw new IOException("no file specified");
if (aborted) {
md.setIncomplete(aborted);
write(save,verbose);
}
else {
write(file, verbose);
save.delete();
}
}
public void write(File f) throws IOException
{
write(f,true);
}
public void write(File f, boolean verbose) throws IOException
{
md.write(f,verbose);
System.out.println(new Date()+": "+f+" done: "+MandUtils.time(md.getInfo().getTime()));
}
private static class Filter {
Set<MandelName> prefix;
boolean fast;
boolean variants;
public void addPrefix(MandelName name)
{
if (prefix==null) prefix=new HashSet<MandelName>();
prefix.add(name);
}
private boolean filterPrefix(MandelName name)
{
if (prefix==null) return true;
for (MandelName p:prefix) {
if (p.isAbove(name)) {
return true;
}
}
return false;
}
public boolean filter(QualifiedMandelName name, PixelIterator pi)
{
if (!filterPrefix(name.getMandelName())) return false;
if (variants&&name.getQualifier()==null) return false;
if (fast&&!pi.isFast()) return false;
return true;
}
}
static public void main(String[] args)
{
int c=0;
boolean sflag=false; // server mode
boolean cflag=false;
boolean dflag=false; // delete obsolete
Filter filter=new Filter();
Set<File> files=new HashSet<File>();
while (args.length>c&&args[c].charAt(0)=='-') {
String arg=args[c++];
for (int i=1; i<arg.length(); i++) {
char opt;
switch (opt=arg.charAt(i)) {
case 's':
sflag=true;
break;
case 'd':
dflag=true;
break;
case 'c':
cflag=true;
break;
case 'f':
filter.fast=true;
break;
case 'v':
filter.variants=true;
break;
case 'p':
if (args.length>c) {
MandelName mn=MandelName.create(args[c++]);
if (mn==null) Error("illegal mandel name '"+args[c-1]+"'");
filter.addPrefix(mn);
}
else Error("name prefix missing");
break;
default:
Error("illegal option '"+opt+"'");
}
}
}
while (args.length>c) {
files.add(new File(args[c++]));
}
if (sflag) {
service(dflag,filter);
}
else {
try {
Environment env=new Environment(null);
for (File f:files) {
try {
Mand m=new Mand(f, env);
m.calculate();
m.write();
if (cflag) {
try {
m.check();
}
catch (Exception e) {
e.printStackTrace(System.err);
Error("check failed: "+e);
}
}
}
catch (IOException ex) {
Error("cannot handle "+f+": "+ex);
}
}
}
catch (IllegalConfigurationException ex) {
Error("illegal config: "+ex);
}
}
}
static void cleanupInfo(Environment env, AbstractFile f)
{
if (!env.backupInfoFile(f)) {
if (env.isCleanupInfo() && f.isFile()) {
System.out.println("deleting "+f);
try {
MandelFolder.Util.delete(f.getFile());
}
catch (IOException ex) {
System.out.println("deletion of "+f+" failed: "+ex);
}
}
}
}
////////////////////////////////////////////////////////////////////////////
private static class Service {
Environment env;
Set<AbstractFile> ignored;
MandelScanner imagescan;
MandelScanner incompletescan;
MandelScanner infoscan;
MandelScanner prioscan;
boolean dflag;
Filter filter;
public Service(boolean dflag, Filter filter) throws IllegalConfigurationException
{
this.dflag=dflag;
this.filter=filter;
env=new Environment(null);
ignored=new HashSet<AbstractFile>();
imagescan=env.getImageDataScanner();
incompletescan=env.getIncompleteScanner();
infoscan=env.getInfoScanner();
prioscan=env.getPrioInfoScanner();
}
public Environment getEnvironment()
{ return env;
}
public void service()
{
Iterator<MandelHandle> fallback=infoscan.getMandelHandles().iterator();
while (true) {
int found=0;
for (MandelHandle h:prioscan.getMandelHandles()) {
found+=handle(h,false);
}
if (found==0) {
if (!fallback.hasNext()) {
System.out.println("rescan standard scanner");
infoscan.rescan(false);
fallback=infoscan.getMandelHandles().iterator();
}
while (found==0 && fallback.hasNext()) {
found+=handle(fallback.next(),true);
}
}
if (found==0) {
//System.out.println("nothing found");
try {
Thread.sleep(1000*20);
}
catch (InterruptedException ie) {
System.exit(1);
}
}
else System.out.println(""+found+" files processed");
//System.out.println("rescan prio scanner");
prioscan.rescan(false);
}
}
MandelData checkOld(MandelScanner scan,
QualifiedMandelName name, MandelInfo reqd,
String msg)
{
MandelData old=null;
Set<MandelHandle> set=scan.getMandelHandles(name);
if (!set.isEmpty()) {
for (MandelHandle h:set) {
if (h.getHeader().hasRaster()) {
try {
MandelData md=h.getData();
MandelInfo mi=md.getInfo();
if (reqd.getDX().equals(mi.getDX())
&&reqd.getDY().equals(mi.getDY())
&&reqd.getXM().equals(mi.getXM())
&&reqd.getYM().equals(mi.getYM())
&&reqd.getRX()==mi.getRX()
&&reqd.getRY()==mi.getRY()) {
System.out.println(msg+" "+h.getFile()+": "+mi.getLimitIt());
old=md;
break;
}
}
catch (IOException io) {
}
}
}
}
return old;
}
int handle(MandelHandle mh, boolean fallback)
{ int found=0;
AbstractFile f=mh.getFile();
QualifiedMandelName name=mh.getName();
if (ignored.contains(f)) return found;
if (name.getLabel()!=null) return found;
//System.out.println("found "+f);
if (!f.getFile().exists()) {
System.out.println("already processed or deleted: "+f);
return found;
}
String n=f.getName();
//System.out.println("checking for image data "+f);
try {
MandelData old=null;
FolderLock lock=MandelFolder.getMandelFolder(f.getFile().getParentFile());
if (!lock.lock()) return found;
try {
if (!f.tryLock()) return found;
}
finally {
lock.releaseLock();
}
System.out.println("got lock for "+f);
MandelData req;
try {
req=new MandelData(f, false);
}
catch (IOException io) {
f.releaseLock();
if (f.getFile().length()==0) f.getFile().delete();
return found;
}
MandelInfo reqd=req.getInfo();
old=checkOld(imagescan,name,reqd,
"requested "+reqd.getLimitIt()+" found");
// TODO: what happen if name is reused for other coordinates???
if (old!=null&&old.getInfo().getLimitIt()>=reqd.getLimitIt()) {
System.out.println(f+" skipped");
lock.lock();
try {
f.releaseLock();
if (dflag) {
cleanupInfo(env, f);
}
else {
ignored.add(f);
}
}
finally {
lock.releaseLock();
}
return found;
}
//////////////
old=checkOld(incompletescan,name,reqd,"resuming");
///////////////
Mand m=new Mand(req, old, name, env);
m.setFilter(filter);
if (!m.calculate()) {
ignored.add(f);
f.releaseLock();
System.out.println(f+" skipped for filter mode");
System.out.println("release lock for "+f);
return found;
}
found++;
m.write(false);
lock.lock();
try {
f.releaseLock();
System.out.println("release lock for "+f);
if (!m.isAborted()) {
if (!f.getName().equals(m.getFile().getName())) {
cleanupInfo(env, f);
}
}
else throw new ShutdownException();
}
finally {
lock.releaseLock();
}
}
catch (IOException io) {
System.err.println("*** "+f+": "+io);
}
return found;
}
}
static private void service(boolean dflag, Filter filter)
{
try {
if (filter!=null) {
if (filter.prefix!=null)
System.out.println("prefix filter is "+filter.prefix);
if (filter.variants) System.out.println("variants filter is on");
if (filter.fast) System.out.println("fast filter is on");
}
Service srv=new Service(dflag, filter);
srv.service();
}
catch (IllegalConfigurationException ex) {
Command.Error("illegal config: "+ex);
}
catch (ShutdownException sd) {
Command.Warning("calculation service aborted!");
}
}
}