/* * XLogo4Schools - A Logo Interpreter specialized for use in schools, based on XLogo by Loic Le Coq * Copyright (C) 2013 Marko Zivkovic * Contact Information: marko88zivkovic at gmail dot com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 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 General * Public License for more details. You should have received a copy of the * GNU General Public License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * This Java source code belongs to XLogo4Schools, written by Marko Zivkovic * during his Bachelor thesis at the computer science department of ETH Zurich, * in the year 2013 and/or during future work. * * It is a reengineered version of XLogo written by Loic Le Coq, published * under the GPL License at http://xlogo.tuxfamily.org/ * * Contents of this file were entirely written by Marko Zivkovic */ package xlogo.storage; import java.io.File; import java.io.IOException; import java.io.Serializable; public abstract class Storable implements Serializable { /** * */ private static final long serialVersionUID = 3506253939129765438L; /** * The file's name with extension */ private String fileName; /** * The Directory where this is stored */ private File location; /** * Dirty : an object is dirty if it was changed since it was created, loaded or stored the last time. */ private transient boolean dirty = true; /** * If true, the storable will only be stored after it has been marked dirty (thus, if an explicit change happened to it) */ private boolean isStoringDeferred = false; /** * Will not be stored if virtual. */ private transient boolean isVirtual = false; /** * @see #isPersisted() */ private boolean isPersisted = false; /* * PATH BUILDERS */ public static File getFile(File dir, String fileName) { return new File(dir.toString() + File.separator + fileName); } public static File getDirectory(File prefix, String dirName) { return new File(prefix.toString() + File.separator + dirName); } /* * Abstract */ /** * Store this object to the file specified by {@link #getFilePath()} if it is dirty and storing is not deferred, but only if it is not virtual. * @throws IOException */ public void store() { if (!isStoringDeferred()){ if (isDirty() && !isVirtual()){ setPersisted(false); try { File file = getFilePath(); if (!mkParentDirs(file)){ return; } storeCopyToFile(file); } catch (Exception ignore) { } setPersisted(true); } } else { setStoringDeferred(false); } } /** * Persist the contents of this {@link Storable} to the specified file. * @param file * @throws IOException * @throws IllegalArgumentException */ public abstract void storeCopyToFile(File file) throws IOException, IllegalArgumentException; /** * Store this object to the specified file, regardless of whether this is virtual or not. * @param file * @throws IOException * @throws IllegalArgumentException - null is not accepted */ /* * file name & location */ public abstract String getFileNameExtension(); public String getFileNamePrefix(){ return ""; } public String getFileName() { return getFileNamePrefix() + getPlainName() + getFileNameExtension(); } /** * @return FileName without file extension */ public String getPlainName() { return fileName; } /** * If this exists on the file system, that file will be renamed.
* If newFileName already existed, it is deleted first.
* @param newFileName - must not be null and must satisfy {@link #checkLegalName(String)}
* @throws IllegalArgumentException - If the provided name is not legal or null.
*/
public void setPlainName(String newFileName) throws IllegalArgumentException
{
if (newFileName == null || newFileName.length() == 0)
throw new IllegalArgumentException("File name must not be null or empty.");
if (!checkLegalName(newFileName))
throw new IllegalArgumentException("The chosen file name contains illegal characters.");
String ext = getFileNameExtension();
String oldName = getPlainName();
String newName = newFileName.endsWith(ext) && newFileName.length() > ext.length() ? newFileName.substring(0,
newFileName.length() - ext.length()) : newFileName;
if (newName.equals(oldName) && oldName != null)
return;
if (isVirtual || oldName == null) {
this.fileName = newFileName;
return;
}
File oldPath = getFilePath();
this.fileName = newName;
if (!oldPath.exists())
return;
File newPath = getFilePath();
if (newPath.exists())
newPath.delete();
oldPath.renameTo(newPath);
}
/**
* @return the directory where this should be stored to.
*/
public File getLocation() {
return location;
}
/**
* To set null or a file that is not a directory or a directory with no write permissions is an error, as long as this is not virtual.
* Setting location has no effect if this is virtual.
* @param location - the directory where this should be stored to. If null, location will be set to user home directory
* @throws IOException
* @throws IOException If the specified location is not a directory or no write permissions exist, or the chosen name is not legal.
*/
public void setLocation(File location) throws IllegalArgumentException {
if (isVirtual) { return; }
if (location == null) {
location = new File(System.getProperty("user.home"));
}
this.location = location;
makeDirty();
}
/**
* If the specified location does not exist yet, it is created using mkdirs.
*/
public void mkDirs() {
mkDirs(location);
}
public void mkDirs(File location) {
if (!location.isDirectory()) {
location.mkdirs();
}
if (!location.isDirectory() || !location.canWrite()) { throw new IllegalArgumentException(
"Cannot store this to specified location : " + location.toString()); }
}
public boolean mkParentDirs(File file) {
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
if (!parent.isDirectory() || !parent.canWrite()) {
return false;
}
return true;
}
/**
* @return the file where this should be stored to. Returns null if {@link getLocation()} returns null.
*/
public File getFilePath() {
if (getLocation() == null)
return null;
return getFile(getLocation(), getFileName());
}
/**
* @return whether the file specified by {@link #getFilePath()} exists.
*/
public boolean existsPhysically() {
if (getFilePath() == null)
return false;
return getFilePath().exists();
}
/*
* isDirty
*/
public boolean isDirty() {
return dirty;
}
/**
* Should be called from every setter that sets a property that should be stored later
* @see StorableObject#makeClean()
*/
protected void makeDirty() {
dirty = true;
setStoringDeferred(false);
}
/**
* Should be called whenever this was synchronized with its version on the file system (load or store)
* @see StorableObject#makeDirty()
*/
protected void makeClean() {
dirty = false;
}
/*
* Deferred
*/
public boolean isStoringDeferred() {
return isStoringDeferred;
}
public void setStoringDeferred(boolean isDeferred) {
this.isStoringDeferred = isDeferred;
}
/*
* isVirtual
*/
/**
* @see #isVirtual()
*/
protected void makeVirtual() {
isVirtual = true;
}
/**
* A virtual object will not be stored on the file system, even though {@link store()} was called.
* This allows to use the application without having an actual user account and without automatic saving.
* @return
*/
public boolean isVirtual() {
return isVirtual;
}
/*
* isPersisted
*/
/**
* @see #isPersisted()
* @param isPersisted
*/
protected void setPersisted(boolean isPersisted){
this.isPersisted = isPersisted;
}
/**
* Whether the last attempt to store or load the object was successful
* @return
*/
public boolean isPersisted(){
return isPersisted;
}
// The best I found : http://stackoverflow.com/questions/893977/java-how-to-find-out-whether-a-file-name-is-valid
// some windows specific chars are not contained...
public static final String ILLEGAL_NAME_CHARACTERS = "/\n\r\t\0\f`?*\\<>|\":";
public static boolean checkLegalName(String name) {
if (name == null || name.length() == 0)
return false;
//StringTokenizer check = new StringTokenizer(name, ILLEGAL_NAME_CHARACTERS, true);
//return (check.countTokens() == 1);
for (char c : name.toCharArray()) {
if (ILLEGAL_NAME_CHARACTERS.indexOf(c) > -1)
return false;
}
return true;
}
}