//bookmarksDB.java 
//-------------------------------------
//part of YACY
//(C) by Michael Peter Christen; mc@anomic.de
//first published on http://www.anomic.de
//Frankfurt, Germany, 2004
//
//This file ist contributed by Alexander Schier
//
//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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//Using this software in any meaning (reading, learning, copying, compiling,
//running) means that you agree that the Author(s) is (are) not responsible
//for cost, loss of data or any harm that may be caused directly or indirectly
//by usage of this softare or this documentation. The usage of this software
//is on your own risk. The installation and usage (starting/running) of this
//software may allow other people or application to access your computer and
//any attached devices and is highly dependent on the configuration of the
//software which must be done by the user of the software; the author(s) is
//(are) also not responsible for proper configuration and usage of the
//software, even if provoked by documentation provided together with
//the software.
//
//Any changes to this file according to the GPL as documented in the file
//gpl.txt aside this file in the shipment you received can be done to the
//lines that follows this copyright notice here, but changes must not be
//done inside the copyright notive above. A re-distribution must contain
//the intact and unchanged copyright notice.
//Contributions and changes to the program code must be marked as such.
package de.anomic.data;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import de.anomic.htmlFilter.htmlFilterContentScraper;
import de.anomic.htmlFilter.htmlFilterWriter;
import de.anomic.plasma.plasmaCondenser;
import de.anomic.plasma.plasmaURL;
import de.anomic.kelondro.kelondroDyn;
import de.anomic.kelondro.kelondroException;
import de.anomic.kelondro.kelondroMapObjects;
import de.anomic.kelondro.kelondroObjects;
import de.anomic.kelondro.kelondroObjectsMapEntry;
import de.anomic.net.URL;
import de.anomic.server.serverFileUtils;
import de.anomic.server.logging.serverLog;

public class bookmarksDB {
    kelondroMapObjects tagsTable;
    //kelondroMap bookmarksTable;
    kelondroObjects bookmarksTable;
    kelondroMapObjects datesTable;
    HashMap tagCache;
    HashMap bookmarkCache;
    
    public static String tagHash(String tagName){
        return plasmaCondenser.word2hash(tagName.toLowerCase());
    }
    public static String tagHash(String tagName, String user){
        return plasmaCondenser.word2hash(user+":"+tagName.toLowerCase());
    }
    public static String dateToiso8601(Date date){
    	return new SimpleDateFormat("yyyy-MM-dd").format(date)+"T"+(new SimpleDateFormat("HH:mm:ss")).format(date)+"Z";
    }
    public static Date iso8601ToDate(String iso8601){
    	String[] tmp=iso8601.split("T");
        if(tmp.length!=2){
            //Error parsing Date
            return new Date();
        }
    	String day=tmp[0];
    	String time=tmp[1];
    	if(time.length()>8){
    		time=time.substring(0,8);
    	}
    	try {
			Calendar date=Calendar.getInstance();
			Calendar date2=Calendar.getInstance();
			date.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(day));
			date2.setTime(new SimpleDateFormat("HH:mm:ss").parse(time));
			
			date.set(Calendar.HOUR_OF_DAY, date2.get(Calendar.HOUR_OF_DAY));
			date.set(Calendar.MINUTE, date2.get(Calendar.MINUTE));
			date.set(Calendar.SECOND, date2.get(Calendar.SECOND));
			
			return date.getTime();
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	return new Date();
    }
    
    public bookmarksDB(File bookmarksFile, File tagsFile, File datesFile, int bufferkb, long preloadTime) {
        // bookmarks
        tagCache=new HashMap();
        bookmarkCache=new HashMap();
        bookmarksFile.getParentFile().mkdirs();
        //this.bookmarksTable = new kelondroMap(kelondroDyn.open(bookmarksFile, bufferkb * 1024, preloadTime, 12, 256, '_', true, false));
        this.bookmarksTable = new kelondroObjects(kelondroDyn.open(bookmarksFile, bufferkb * 1024, preloadTime, 12, 256, '_', true, false), 1000);

        // tags
        tagsFile.getParentFile().mkdirs();
        boolean tagsFileExisted = tagsFile.exists();
        this.tagsTable = new kelondroMapObjects(kelondroDyn.open(tagsFile, bufferkb * 1024, preloadTime, 12, 256, '_', true, false), 500);
        if (!tagsFileExisted) rebuildTags();

        // dates
        boolean datesExisted = datesFile.exists();
        this.datesTable = new kelondroMapObjects(kelondroDyn.open(datesFile, bufferkb * 1024, preloadTime, 20, 256, '_', true, false), 500);
        if (!datesExisted) rebuildDates();
        
    }
    
    public void close(){
        try {
            //flushBookmarkCache();
            bookmarksTable.close();
        } catch (IOException e) {}
        try {
            flushTagCache();
            tagsTable.close();
        } catch (IOException e) {}
        try {
            datesTable.close();
        } catch (IOException e) {}
    }
    public int bookmarksSize(){
        return bookmarksTable.size();
    }
    public int tagSize(boolean flushed){
        if(flushed)
            flushTagCache();
        return tagsTable.size();
    }
    public int tagsSize(){
        return tagSize(false);
    }
    public void saveBookmark(Bookmark bookmark){
        try {
            bookmarksTable.set(bookmark.getUrlHash(), bookmark);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public Tag loadTag(String hash){
        Map map;
        Tag ret=null;
        map = tagsTable.getMap(hash);
        if(map!=null){
            ret=new Tag(hash, map);
            tagCache.put(hash, ret);
        }
        
        return ret;
    }
    public void saveTag(Tag tag){
        if(tag!=null){
            tagCache.put(tag.getTagHash(), tag);
        }
    }
    /**
     * store a Tag in the tagsDB or remove an empty tag
     * @param tag the tagobject to be stored/removed
     */
    public void storeTag(Tag tag){
        try {
            if(tag.size() >0){
                bookmarksDB.this.tagsTable.set(tag.getTagHash(), tag.getMap());
            }else{
                bookmarksDB.this.tagsTable.remove(tag.getTagHash());
            }
        } catch (IOException e) {}
    }
    public void flushTagCache(){
        Iterator it=tagCache.keySet().iterator();
        while(it.hasNext()){
            storeTag((Tag) tagCache.get(it.next()));
        }
        tagCache=new HashMap();
    }
    
    public String addTag(Tag tag){
        //tagsTable.set(tag.getTagName(), tag.getMap());
        tagCache.put(tag.getTagHash(), tag);
        return tag.getTagName();
    }
    public void rebuildTags(){
        serverLog.logInfo("BOOKMARKS", "rebuilding tags.db from bookmarks.db...");
        Iterator it=bookmarkIterator(true);
        Bookmark bookmark;
        Tag tag;
        String[] tags;
        while(it.hasNext()){
            bookmark=(Bookmark) it.next();
            tags = bookmark.getTagsString().split(",");
            tag=null;
            for(int i=0;i<tags.length;i++){
                tag=getTag(tagHash(tags[i]));
                if(tag==null){
                    tag=new Tag(tags[i]);
                }
                tag.addUrl(bookmark.getUrlHash());
                saveTag(tag);
            }
        }
        flushTagCache();
        serverLog.logInfo("BOOKMARKS", "Rebuilt "+tagsTable.size()+" tags using your "+bookmarksTable.size()+" bookmarks.");
    }
    public void rebuildDates(){
        serverLog.logInfo("BOOKMARKS", "rebuilding dates.db from bookmarks.db...");
        Iterator it=bookmarkIterator(true);
        Bookmark bookmark;
        String date;
        bookmarksDate bmDate;
        while(it.hasNext()){
            bookmark=(Bookmark) it.next();
            date = (new SimpleDateFormat("yyyy-MM-dd")).format(new Date(bookmark.getTimeStamp()));
            bmDate=getDate(date);
            if(bmDate==null){
                bmDate=new bookmarksDate(date);
            }
            bmDate.add(bookmark.getUrlHash());
            bmDate.setDatesTable();
        }
        serverLog.logInfo("BOOKMARKS", "Rebuilt "+datesTable.size()+" dates using your "+bookmarksTable.size()+" bookmarks.");
    }
    public Tag getTag(String hash){
        if(tagCache.containsKey(hash)){
            return (Tag) tagCache.get(hash);
        }
        return loadTag(hash); //null if it does not exists
    }
    public bookmarksDate getDate(String date){
        Map map;
        map=datesTable.getMap(date);
        if(map==null) return new bookmarksDate(date);
        return new bookmarksDate(date, map);
    }
    public boolean renameTag(String oldName, String newName){
    	String tagHash=tagHash(oldName);
        Tag tag=getTag(tagHash);
            if (tag != null) {
            HashSet urlHashes = tag.getUrlHashes();
            try {
                if(tagCache.containsKey(tagHash(oldName))){
                    tagCache.remove(tagHash(oldName));
                }
                tagsTable.remove(tagHash(oldName));
            } catch (IOException e) {
            }
            tag=new Tag(tagHash(newName), tag.getMap());
            saveTag(tag);
            Iterator it = urlHashes.iterator();
            Bookmark bookmark;
            HashSet tags;
            while (it.hasNext()) {
                bookmark = getBookmark((String) it.next());
                tags = bookmark.getTags();
                tags.remove(oldName); //this will fail, if upper/lowercase is not matching
                tags.add(newName);
                bookmark.setTags(tags, true);
                saveBookmark(bookmark);
            }
            return true;
        }
        return false;
    }
    public void removeTag(String hash){
        try {
            if(tagCache.containsKey(hash)){
                tagCache.remove(hash);
            }
            tagsTable.remove(hash);
        } catch (IOException e) {}
    }
    public String addBookmark(Bookmark bookmark){
        saveBookmark(bookmark);
        return bookmark.getUrlHash();
 
    }
    public Bookmark getBookmark(String urlHash){
        try {
            return (Bookmark) bookmarksTable.get(urlHash);
        } catch (IOException e) {
            return null;
        }
        
    }
    public Iterator getBookmarksIterator(boolean priv){
        TreeSet set=new TreeSet(new bookmarkComparator(true));
        Iterator it=bookmarkIterator(true);
        Bookmark bm;
        while(it.hasNext()){
            bm=(Bookmark)it.next();
            if(priv || bm.getPublic()){
            	set.add(bm.getUrlHash());
            }
        }
        return set.iterator();
    }
    public Iterator getBookmarksIterator(String tagName, boolean priv){
        TreeSet set=new TreeSet(new bookmarkComparator(true));
        String tagHash=tagHash(tagName);
        Tag tag=getTag(tagHash);
        HashSet hashes=new HashSet();
        if(tag != null){
            hashes=getTag(tagHash).getUrlHashes();
        }
        if(priv){
        	set.addAll(hashes);
        }else{
        	Iterator it=hashes.iterator();
        	Bookmark bm;
        	while(it.hasNext()){
        		bm=getBookmark((String) it.next());
        		if(bm.getPublic()){
        			set.add(bm.getUrlHash());
        		}
        	}
        }
    	return set.iterator();
    }
    public Iterator getTagIterator(boolean priv){
    	TreeSet set=new TreeSet(new tagComparator());
    	Iterator it=tagIterator(true);
    	Tag tag;
    	while(it.hasNext()){
    		tag=(Tag) it.next();
    		if(priv ||tag.hasPublicItems()){
    			set.add(tag);
    		}
    	}
    	return set.iterator();
    }
    public boolean removeBookmark(String urlHash){
        Bookmark bookmark = getBookmark(urlHash);
        if(bookmark == null) return false; //does not exist
        HashSet tags = bookmark.getTags();
        bookmarksDB.Tag tag=null;
        Iterator it=tags.iterator();
        while(it.hasNext()){
            tag=getTag( tagHash((String) it.next()) );
            if(tag!=null){
                tag.delete(urlHash);
                saveTag(tag);
            }
        }
        Bookmark b;
        try {
            b = (Bookmark) bookmarksTable.get(urlHash);
            bookmarksTable.remove(urlHash);
        } catch (IOException e) {
            b = null;
        }
        return b != null;
    }

    public Bookmark createBookmark(String url, String user){
        if (url == null || url.length() == 0) return null;
        Bookmark bk = new Bookmark(url);
        bk.setOwner(user);
        return (bk.getUrlHash() == null || bk.toMap() == null) ? null : bk;
    }
    public Iterator tagIterator(boolean up){
        try {
            return new tagIterator(up);
        } catch (IOException e) {
            return new HashSet().iterator();
        }
    }
    public Iterator bookmarkIterator(boolean up){
        try {
            return new bookmarkIterator(up);
        } catch (IOException e) {
            return new HashSet().iterator();
        }
    }
    public void addBookmark(String url, String title, ArrayList tags){
        
    }
    
    public int importFromBookmarks(URL baseURL, String input, String tag, boolean importPublic){
		try {
			// convert string to inputstream
			ByteArrayInputStream byteIn = new ByteArrayInputStream(input.getBytes("UTF-8"));
			InputStreamReader reader = new InputStreamReader(byteIn,"UTF-8");
			
			// import stream
			return this.importFromBookmarks(baseURL,reader,tag,importPublic);
		} catch (UnsupportedEncodingException e) { 
			return 0;
		}        	
    }
    public int importFromBookmarks(URL baseURL, InputStreamReader input, String tag, boolean importPublic){
    	int importCount = 0;
    	
    	HashMap links=new HashMap();
    	Iterator it;
    	String url,title;
    	Bookmark bm;
    	HashSet tags=listManager.string2hashset(tag); //this allow multiple default tags
    	try {
    		//load the links
    		htmlFilterContentScraper scraper = new htmlFilterContentScraper(baseURL);
    		//OutputStream os = new htmlFilterOutputStream(null, scraper, null, false);
    		Writer writer= new htmlFilterWriter(null,null,scraper, null, false);
    		serverFileUtils.copy(input,writer);
    		writer.close();
    		links = (HashMap) scraper.getAnchors();
    	} catch (IOException e) {}
    	it=links.keySet().iterator();
    	while(it.hasNext()){
    		url=(String) it.next();
    		title=(String) links.get(url);
    		if(title.equals("")){//cannot be displayed
    			title=url;
    		}
    		bm=new Bookmark(url);
    		bm.setProperty(Bookmark.BOOKMARK_TITLE, title);
    		bm.setTags(tags);
    		bm.setPublic(importPublic);
    		saveBookmark(bm);
    		
    		importCount++;
    	}

    	flushTagCache();
    	
    	return importCount;
    }
    
    public int importFromXML(String input, boolean importPublic){    	
		try {
			// convert string to inputstream
			ByteArrayInputStream byteIn = new ByteArrayInputStream(input.getBytes("UTF-8"));
			
			// import stream
			return this.importFromXML(byteIn,importPublic);
		} catch (UnsupportedEncodingException e) { 
			return 0;
		}    	
    }
    
    public int importFromXML(InputStream input, boolean importPublic){
    	DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
    	factory.setValidating(false);
    	factory.setNamespaceAware(false);
    	DocumentBuilder builder;
    	try {
    		builder = factory.newDocumentBuilder();
    		Document doc=builder.parse(input);
    		return parseXMLimport(doc, importPublic);
    	} catch (ParserConfigurationException e) {  
    	} catch (SAXException e) {
    	} catch (IOException e) {
    	}
    	return 0;
    	
    }
    public int parseXMLimport(Node doc, boolean importPublic){
    	int importCount = 0;
        if(doc.getNodeName()=="post"){
            NamedNodeMap attributes = doc.getAttributes();
            String url=attributes.getNamedItem("href").getNodeValue();
            if(url.equals("")){
                return 0;
            }
            Bookmark bm=new Bookmark(url);
            String tagsString="";
            String title="";
            String description="";
            String time="";
            if(attributes.getNamedItem("tag")!=null){
                tagsString=attributes.getNamedItem("tag").getNodeValue();
            }
            if(attributes.getNamedItem("description")!=null){
                title=attributes.getNamedItem("description").getNodeValue();
            }
            if(attributes.getNamedItem("extended")!=null){
                description=attributes.getNamedItem("extended").getNodeValue();
            }
            if(attributes.getNamedItem("time")!=null){
                time=attributes.getNamedItem("time").getNodeValue();
            }
            HashSet tags=new HashSet();
            
            if(title != null){
                bm.setProperty(Bookmark.BOOKMARK_TITLE, title);
            }
            if(tagsString!=null){
                tags = listManager.string2hashset(tagsString.replace(' ', ','));
            }
            bm.setTags(tags, true);
            if(time != null){
                bm.setTimeStamp(iso8601ToDate(time).getTime());
            }
            if(description!=null){
                bm.setProperty(Bookmark.BOOKMARK_DESCRIPTION, description);
            }
            bm.setPublic(importPublic);
            saveBookmark(bm);
            
            importCount++;
        }
        NodeList children=doc.getChildNodes();
        if(children != null){
            for (int i=0; i<children.getLength(); i++) {
            	importCount += parseXMLimport(children.item(i), importPublic);
            }
        }
        flushTagCache();
        
        return importCount;
    }
    /**
     * Subclass, which stores an Tag
     *
     */
    public class Tag{
        public static final String URL_HASHES="urlHashes";
        public static final String TAG_NAME="tagName";
        private String tagHash;
        private Map mem;
        private HashSet urlHashes;

        public Tag(String hash, Map map){
        	tagHash=hash;
            mem=map;
            if(mem.containsKey(URL_HASHES))
                urlHashes=listManager.string2hashset((String) mem.get(URL_HASHES));
            else
                urlHashes=new HashSet();
        }
        public Tag(String name, HashSet entries){
            tagHash=tagHash(name);
            mem=new HashMap();
            //mem.put(URL_HASHES, listManager.arraylist2string(entries));
            urlHashes=entries;
            mem.put(TAG_NAME, name);
        }
        public Tag(String name){
            tagHash=tagHash(name);
            mem=new HashMap();
            //mem.put(URL_HASHES, "");
            urlHashes=new HashSet();
            mem.put(TAG_NAME, name);
        }
        public Map getMap(){
            mem.put(URL_HASHES, listManager.hashset2string(this.urlHashes));
            return mem;
        }
        /**
         * get the lowercase Tagname
         */
        public String getTagName(){
            /*if(this.mem.containsKey(TAG_NAME)){
                return (String) this.mem.get(TAG_NAME);
            }
            return "";*/
            return getFriendlyName().toLowerCase();
        }
        public String getTagHash(){
            return tagHash;
        }
        /**
         * get tag name, with all uppercase chars.
         * @return
         */
        public String getFriendlyName(){
            /*if(this.mem.containsKey(TAG_FRIENDLY_NAME)){
                return (String) this.mem.get(TAG_FRIENDLY_NAME);
            }
            return getTagName();*/
            if(this.mem.containsKey(TAG_NAME)){
                return (String) this.mem.get(TAG_NAME);
            }
            return "notagname";
        }
        public HashSet getUrlHashes(){
            return urlHashes;
        }
        public boolean hasPublicItems(){
        	Iterator it=getBookmarksIterator(this.getTagHash(), false);
        	if(it.hasNext()){
        		return true;
        	}
        	return false;
        }
        public void addUrl(String urlHash){
            urlHashes.add(urlHash);
        }
        public void delete(String urlHash){
            urlHashes.remove(urlHash);
        }
        public int size(){
            return urlHashes.size();
        }
    }
    public class bookmarksDate{
        public static final String URL_HASHES="urlHashes";
        private Map mem;
        String date;

        public bookmarksDate(String mydate){
            //round to seconds, but store as milliseconds (java timestamp)
            date=String.valueOf((Long.parseLong(mydate)/1000)*1000);
            mem=new HashMap();
            mem.put(URL_HASHES, "");
        }

        public bookmarksDate(String mydate, Map map){
            //round to seconds, but store as milliseconds (java timestamp)
            date=String.valueOf((Long.parseLong(mydate)/1000)*1000);
            mem=map;
        }
        public bookmarksDate(String mydate, ArrayList entries){
            //round to seconds, but store as milliseconds (java timestamp)
            date=String.valueOf((Long.parseLong(mydate)/1000)*1000);
            mem=new HashMap();
            mem.put(URL_HASHES, listManager.arraylist2string(entries));
        }
        public void add(String urlHash){
            String urlHashes = (String)mem.get(URL_HASHES);
            ArrayList list;
            if(urlHashes != null && !urlHashes.equals("")){
                list=listManager.string2arraylist(urlHashes);
            }else{
                list=new ArrayList();
            }
            if(!list.contains(urlHash) && urlHash != null && !urlHash.equals("")){
                list.add(urlHash);
            }
            this.mem.put(URL_HASHES, listManager.arraylist2string(list));
            /*if(urlHashes!=null && !urlHashes.equals("") ){
                if(urlHashes.indexOf(urlHash) <0){
                    this.mem.put(URL_HASHES, urlHashes+","+urlHash);
                }
            }else{
                this.mem.put(URL_HASHES, urlHash);
            }*/
        }
        public void delete(String urlHash){
            ArrayList list=listManager.string2arraylist((String) this.mem.get(URL_HASHES));
            if(list.contains(urlHash)){
                list.remove(urlHash);
            }
            this.mem.put(URL_HASHES, listManager.arraylist2string(list));
        }
        public void setDatesTable(){
            try {
                if(this.size() >0){
                    bookmarksDB.this.datesTable.set(getDateString(), mem);
                }else{
                    bookmarksDB.this.datesTable.remove(getDateString());
                }
            } catch (IOException e) {}
        }
        public String getDateString(){
            return date;
        }
        public ArrayList getBookmarkList(){
            return listManager.string2arraylist((String)this.mem.get(URL_HASHES));
        }
        public int size(){
            return listManager.string2arraylist(((String)this.mem.get(URL_HASHES))).size();
        }
    }
    /**
     * Subclass, which stores the bookmark
     *
     */
    public class Bookmark extends kelondroObjectsMapEntry{
        public static final String BOOKMARK_URL="bookmarkUrl";
        public static final String BOOKMARK_TITLE="bookmarkTitle";
        public static final String BOOKMARK_DESCRIPTION="bookmarkDesc";
        public static final String BOOKMARK_TAGS="bookmarkTags";
        public static final String BOOKMARK_PUBLIC="bookmarkPublic";
        public static final String BOOKMARK_TIMESTAMP="bookmarkTimestamp";
        public static final String BOOKMARK_OWNER="bookmarkOwner";
        private String urlHash;
        private HashSet tags;
        private long timestamp;
        public Bookmark(String urlHash, Map map){
            super(map);
            this.urlHash=urlHash;
            if(map.containsKey(BOOKMARK_TAGS))
                tags=listManager.string2hashset((String) map.get(BOOKMARK_TAGS));
            else
                tags=new HashSet();
            loadTimestamp();
        }
        public Bookmark(String url){
            super();
            if(!url.toLowerCase().startsWith("http://")){
                url="http://"+url;
            }
            this.urlHash=plasmaURL.urlHash(url);
            entry.put(BOOKMARK_URL, url);
            this.timestamp=System.currentTimeMillis();
            tags=new HashSet();
            Bookmark oldBm=getBookmark(this.urlHash);
            if(oldBm!=null && oldBm.entry.containsKey(BOOKMARK_TIMESTAMP)){
                entry.put(BOOKMARK_TIMESTAMP, oldBm.entry.get(BOOKMARK_TIMESTAMP)); //preserve timestamp on edit
            }else{
                entry.put(BOOKMARK_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
            }  
            bookmarksDate bmDate=getDate((String) entry.get(BOOKMARK_TIMESTAMP));
            bmDate.add(this.urlHash);
            bmDate.setDatesTable();
            
            removeBookmark(this.urlHash); //prevent empty tags
        }
        public Bookmark(String urlHash, URL url){
            super();
            this.urlHash=urlHash;
            entry.put(BOOKMARK_URL, url.toString());
            tags=new HashSet();
            timestamp=System.currentTimeMillis();
        }
        public Bookmark(String urlHash, String url){
            super();
            this.urlHash=urlHash;
            entry.put(BOOKMARK_URL, url);
            tags=new HashSet();
            timestamp=System.currentTimeMillis();
        }
       
        private Map toMap(){
            entry.put(BOOKMARK_TAGS, listManager.hashset2string(tags));
            entry.put(BOOKMARK_TIMESTAMP, String.valueOf(this.timestamp));
            return entry;
        }
        private void loadTimestamp(){
            if(entry.containsKey(BOOKMARK_TIMESTAMP))
                this.timestamp=Long.parseLong((String)entry.get(BOOKMARK_TIMESTAMP));
        }
        public String getUrlHash(){
            return urlHash;
        }
        public String getUrl(){
            return (String) entry.get(BOOKMARK_URL);
        }
        public HashSet getTags(){
            return tags;
        }
        public String getTagsString(){
            return listManager.hashset2string(getTags());
        }
        public String getDescription(){
            if(entry.containsKey(BOOKMARK_DESCRIPTION)){
                return (String) entry.get(BOOKMARK_DESCRIPTION);
            }
            return "";
        }
        public String getTitle(){
            if(entry.containsKey(BOOKMARK_TITLE)){
                return (String) entry.get(BOOKMARK_TITLE);
            }
            return (String) entry.get(BOOKMARK_URL);
        }
        public String getOwner(){
            if(entry.containsKey(BOOKMARK_OWNER)){
                return (String) entry.get(BOOKMARK_OWNER);
            }else{
                return null; //null means admin
            }
        }
        public void setOwner(String owner){
            entry.put(BOOKMARK_OWNER, owner);
        }
        public boolean getPublic(){
            if(entry.containsKey(BOOKMARK_PUBLIC)){
                return ((String) entry.get(BOOKMARK_PUBLIC)).equals("public");
            }else{
                return false;
            }
        }
        public void setPublic(boolean isPublic){
        	if(isPublic){
                entry.put(BOOKMARK_PUBLIC, "public");
        	}else{
                entry.put(BOOKMARK_PUBLIC, "private");
        	}
        }
        public void setProperty(String name, String value){
            entry.put(name, value);
            //setBookmarksTable();
        }
        public void addTag(String tag){
            tags.add(tag);
        }
        /**
         * set the Tags of the bookmark, and write them into the tags table.
         * @param tags a ArrayList with the tags
         */
        public void setTags(HashSet tags){
            setTags(tags, true);
        }
        /**
         * set the Tags of the bookmark
         * @param tags ArrayList with the tagnames
         * @param local sets, whether the updated tags should be stored to tagsDB
         */
        public void setTags(HashSet mytags, boolean local){
            tags.addAll(mytags);
            Iterator it=tags.iterator();
            while(it.hasNext()){
                String tagName=(String) it.next();
                Tag tag=getTag(tagHash(tagName));
                if(tag == null){
                    tag=new Tag(tagName);
                }
                tag.addUrl(getUrlHash());
                if(local){
                    saveTag(tag);
                }
            }
            toMap();
        }

        public long getTimeStamp(){
            return timestamp;
        }
        public void setTimeStamp(long ts){
        	this.timestamp=ts;
        }
    }
    public class tagIterator implements Iterator{
        kelondroDyn.dynKeyIterator tagIter;
        bookmarksDB.Tag nextEntry;
        public tagIterator(boolean up) throws IOException {
            flushTagCache(); //XXX: This costs performace :-((
            this.tagIter = bookmarksDB.this.tagsTable.keys(up, false);
            this.nextEntry = null;
        }
        public boolean hasNext() {
            try {
                return this.tagIter.hasNext();
            } catch (kelondroException e) {
                //resetDatabase();
                return false;
            }
        }
        public Object next() {
            try {
                return getTag((String) this.tagIter.next());
            } catch (kelondroException e) {
                //resetDatabase();
                return null;
            }
        }
        public void remove() {
            if (this.nextEntry != null) {
                try {
                    String tagHash = (String)this.nextEntry.getTagHash();
                    if (tagHash != null) removeTag((String) tagHash);
                } catch (kelondroException e) {
                    //resetDatabase();
                }
            }
        }
    }
    public class bookmarkIterator implements Iterator{
        Iterator bookmarkIter;
        bookmarksDB.Bookmark nextEntry;
        public bookmarkIterator(boolean up) throws IOException {
            //flushBookmarkCache(); //XXX: this will cost performance
            this.bookmarkIter = bookmarksDB.this.bookmarksTable.keys(up, false);
            this.nextEntry = null;
        }
        public boolean hasNext() {
            try {
                return this.bookmarkIter.hasNext();
            } catch (kelondroException e) {
                //resetDatabase();
                return false;
            }
        }
        public Object next() {
            try {
                return getBookmark((String) this.bookmarkIter.next());
            } catch (kelondroException e) {
                //resetDatabase();
                return null;
            }
        }
        public void remove() {
            if (this.nextEntry != null) {
                try {
                    Object bookmarkName = this.nextEntry.getUrlHash();
                    if (bookmarkName != null) removeBookmark((String) bookmarkName);
                } catch (kelondroException e) {
                    //resetDatabase();
                }
            }
        }
    }
    /**
     * Comparator to sort the Bookmarks with Timestamps
     */
    public class bookmarkComparator implements Comparator{
        
        private boolean newestFirst;
        /**
         * @param newestFirst newest first, or oldest first?
         */
        public bookmarkComparator(boolean newestFirst){
            this.newestFirst=newestFirst;
        }
        public int compare(Object obj1, Object obj2){
            Bookmark bm1=getBookmark((String)obj1);
            Bookmark bm2=getBookmark((String)obj2);
            //XXX: what happens, if there is a big difference? (to much for int)
            /*if(this.newestFirst){
                return (new Long(bm2.getTimeStamp() - bm1.getTimeStamp())).intValue();
            }else{
                return (new Long(bm1.getTimeStamp() - bm2.getTimeStamp())).intValue();
            }*/
            if(this.newestFirst){
                if(bm2.getTimeStamp() - bm1.getTimeStamp() >0)
                    return 1;
                else
                    return -1;
            }else{
                if(bm1.getTimeStamp() - bm2.getTimeStamp() >0)
                    return 1;
                else
                    return -1;
            }
        }
    }
    /**
     * sorts the tag for name
     */
    public class tagComparator implements Comparator{
    	public int compare(Object obj1, Object obj2){
    		return ((Tag)obj1).getTagName().compareTo(((Tag)obj2).getTagName());
    	}
    }
}