FileLinkedList.java

package eu.javaexperience.io;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import eu.javaexperience.io.FileLinkedList.Link;
import eu.javaexperience.io.file.FileTools;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.semantic.references.MayNull;

/**
 * A linked list stores every entry on disc in a specified directory
 * 
 * Not protected from cyclic referencing. 
 * 
 * add new link:
 * 		- create a new link, by default the id is a newly assigned id
 * 			and parent id is zero, file creatated so
 * 
 * 
 * 
 * */
public class FileLinkedList<T extends Serializable> implements Iterable<Link<T>>
{
	/**
	 * Files in dir:
	 * */
	
	protected final File dir;
	
	public FileLinkedList(File dir)
	{
		if(!dir.exists() || !dir.isDirectory())
		{
			throw new RuntimeException("Given file is not a directory");
		}
		this.dir = dir;
		
		load();
	}
	
	protected void load()
	{
		String[] files = dir.list();

		//load all and create all Link<T> wrapper.
		//determine startpoints, endpoints, max_id
		
		HashMap<Integer, Link<T>> link_registry = new HashMap<>();
		
		//loading elements, find max index
		for(String f: files)
		{
			int index = f.indexOf('.');
			
			if(index < 1)
			{
				continue;
			}
			
			String s1 = f.substring(0, index);
			String s2 = f.substring(index+1, f.length());
			
			int id = Integer.parseInt(s1);
			int pid = Integer.parseInt(s2);
			Link<T> l = new Link<>(this);
			l.id = id;
			if(max_id < id)
			{
				max_id = id;
			}
			
			l.tmp_parent_id = pid;
			link_registry.put(id, l);
			entrys.add(l);
		}
		
		//linking with each others
		for(Link<T> l:entrys)
		{
			if(0 == l.tmp_parent_id)
			{
				continue;
			}

			Link<T> parent = link_registry.get(l.tmp_parent_id);
			//looks like parent destroyed
			
			if(parent == l)//break self referencing 
			{
				parent = null;
			}
			
			if(null == parent)
			{
				l.on_parent_not_found(l.tmp_parent_id);
			}
			else
			{
				l.parent = parent;
				++parent.refcount;
			}
		}
		
		//who has null parent is root, who not referenced is endpoint
		//identify root and endpoints
		for(Link<T> l:entrys)
		{
			l.file = l.generateFileName();
			if(null == l.parent)
			{
				headLinks.add(l);
			}

			if(l.refcount == 0)
			{
				tailLinks.add(l);
			}
		}
	}
	
	//info object: protected Link
	protected Set<Link<T>> entrys = new HashSet<>();
	
	protected final Set<Link<T>> headLinks = new HashSet<>();
	protected final Set<Link<T>> tailLinks = new HashSet<>();
	
	public void fillHeadLinks(Collection<Link<T>> heads)
	{
		heads.addAll(headLinks);
	}
	
	public void fillTailLinks(Collection<Link<T>> tails)
	{
		tails.addAll(tailLinks);
	}
	
	protected int max_id = 0;
	
	public Link<T> newLink(@MayNull Link<T> parent)
	{
		Link<T> l = new Link<>(this, ++max_id, parent);
		return l;
	}
	
	
	public static class Link<T extends Serializable>
	{
		protected transient int tmp_parent_id;
		protected transient int refcount;
		
		protected final FileLinkedList<T> owner;
		
		protected Link(FileLinkedList<T> owner)
		{
			this.owner = owner;
		}
		
		protected Link(FileLinkedList<T> owner, int id, Link<T> parent)
		{
			this(owner);
			this.id = id;
			setParent(parent, true);
			touch();
			owner.entrys.add(this);
			owner.tailLinks.add(this);
		}
		
		protected int id;
		protected File file;
		protected Link<T> parent;
		
		protected int getParentId()
		{
			if(null == parent)
			{
				return 0;
			}
			else
			{
				return parent.id;
			}
		}
		
		protected boolean touch()
		{
			try
			{
				return file.createNewFile();
			}
			catch (IOException e)
			{
				Mirror.throwSoftOrHardButAnyway(e);
				return false;//have a nice day
			}
		}
		
		public FileLinkedList<T> getOwner()
		{
			return owner;
		}

		public File _generateFileName(int parent)
		{
			return new File(owner.dir+"/"+id+"."+parent);
		}
		
		public File generateFileName()
		{
			return _generateFileName(getParentId());
		}
		
		@SuppressWarnings("unchecked")
		public T getContent()
		{
			return (T) SerializationTools.deserializeFromFile(file);
		}
		
		public void setContent(T elem)
		{
			SerializationTools.serializeIntoFile(file, elem);
		}
		
		public Link<T> getParent()
		{
			return parent;
		}
		
		public boolean isRootNode()
		{
			return null == parent;
		}
		
		public boolean isEndpoint()
		{
			return owner.tailLinks.contains(this);
		}
		
		public void setParent(Link<T> l)
		{
			setParent(l, false);
		}
		
		/**
		 * If this is a standalone element you can set the parent.
		 * */
		protected void setParent(Link<T> l, boolean first)
		{
			if(!first && parent == l)
			{
				//nothing changed
				return;
			}
			
			//fool test
			if(null != l && owner != l.owner)
			{
				throw new RuntimeException("Mixing Link<T> between different FileLinkedList is an illegal operation. (yet)");
			}
			
			//this node previously was a rootNode now remove from roots
			if(null == parent)//previously checked that new parent is not null.
			{
				owner.headLinks.remove(this);
			}
			else
			{
				--parent.refcount;
				//check i'm the only referencing element,
				//if yes: the parent will be a tail element
				if(0 == parent.refcount)
				{
					owner.tailLinks.add(parent);
				}
			}
			
			if(null == l)
			{
				//new this will be a head node
				owner.headLinks.add(this);
			}
			else
			{
				++l.refcount;
				//if the parent is an endpoint, we inheit it's position so now
				//this now this an endpoint. 
				if(l.isEndpoint())
				{
					owner.tailLinks.remove(l);
					owner.tailLinks.add(this);
				}
			}
			
			parent = l;
			
			if(!first)
			{
				File oldFile = file;
				file = generateFileName();//new name generated to the new parent
				FileTools.move(oldFile, file);//move to the right new name.
			}
			else
			{
				//if first set parent, just generate path name
				file = generateFileName();
			}
		}
		
		/**
		 * If Link<T> between not found at load time.
		 * This moves to root and set as root element
		 * */
		protected void on_parent_not_found(int parent)
		{
			File old = _generateFileName(parent);
			this.parent = null;
			this.file = generateFileName();
			FileTools.move(old, this.file);
		}
	}

	@Override
	public Iterator<Link<T>> iterator()
	{
		return (Iterator<Link<T>>) entrys.iterator();
	}
}