001    /*
002      Copyright (C) 2001-2003 Laurent Martelli <laurent@aopsys.com>
003    
004      This program is free software; you can redistribute it and/or modify
005      it under the terms of the GNU Lesser General Public License as
006      published by the Free Software Foundation; either version 2 of the
007      License, or (at your option) any later version.
008    
009      This program is distributed in the hope that it will be useful,
010      but WITHOUT ANY WARRANTY; without even the implied warranty of
011      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012      GNU Lesser General Public License for more details.
013    
014      You should have received a copy of the GNU Lesser General Public License
015      along with this program; if not, write to the Free Software
016      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
017    
018    package org.objectweb.jac.aspects.persistence;
019    
020    import java.io.BufferedReader;
021    import java.io.BufferedWriter;
022    import java.io.File;
023    import java.io.FileDescriptor;
024    import java.io.FileInputStream;
025    import java.io.FileNotFoundException;
026    import java.io.FileOutputStream;
027    import java.io.IOException;
028    import java.io.InputStreamReader;
029    import java.io.OutputStream;
030    import java.io.OutputStreamWriter;
031    import java.io.PrintWriter;
032    import java.io.StreamTokenizer;
033    import java.io.Writer;
034    import java.util.Collection;
035    import java.util.Hashtable;
036    import java.util.Iterator;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Properties;
040    import java.util.Set;
041    import java.util.Vector;
042    import org.apache.log4j.Logger;
043    import org.objectweb.jac.aspects.naming.NameGenerator;
044    import org.objectweb.jac.core.rtti.ClassItem;
045    import org.objectweb.jac.core.rtti.CollectionItem;
046    import org.objectweb.jac.core.rtti.FieldItem;
047    import org.objectweb.jac.util.Files;
048    import org.objectweb.jac.util.Log;
049    import org.objectweb.jac.util.Semaphore;
050    import org.objectweb.jac.util.Strings;
051    
052    /**
053     * A FileSystem storage
054     */
055    
056    public class FSStorage implements Storage {
057        static Logger logger = Logger.getLogger("persistence.storage");
058    
059        // name -> oid 
060        private Hashtable names = new Hashtable();
061        // oid -> name
062        private Hashtable oids = new Hashtable();
063        // oid -> classid
064        private Hashtable classes = new Hashtable();
065       
066        private File basedir;
067        private File oidsFile;
068        private File classesFile;
069    
070        /* The encoding of files */
071        protected String encoding;
072    
073        private long lastOID = 0;
074    
075        Semaphore semaphore = new Semaphore(1);
076    
077        NameGenerator nameGen;
078        File nameCountersFile;
079    
080        /**
081         * Create a new file system storage with the default encoding
082         * @param basedirName name of the directory where to store data files
083         */
084        public FSStorage(PersistenceAC ac,
085                         String basedirName) throws Exception {
086            this(ac,basedirName,System.getProperty("file.encoding"));
087        }
088    
089        /**
090         * Create a new file system storage
091         * @param basedirName name of the directory where to store data files
092         * @param encoding the encoding to use for files
093         */
094        public FSStorage(PersistenceAC ac, 
095                         String basedirName, String encoding) 
096            throws Exception 
097        {
098            this.ac = ac;
099            logger.debug("new FSStorage(basedir="+basedirName+", encoding="+encoding+")");
100            this.encoding = encoding;
101            basedir = new File(Files.expandFileName(basedirName));
102            if (!basedir.isAbsolute()) {
103                basedir = new File(org.objectweb.jac.core.Jac.getJacRoot(),basedir.toString());
104            }
105            if (! basedir.isDirectory()) {
106                basedir.mkdirs();
107            }
108    
109            nameGen = new NameGenerator();
110    
111            nameCountersFile = new File(basedir,"nameCounters");
112            try {
113                nameCountersFile.createNewFile();
114                readNameCounters();
115            } catch(Exception e) {
116                e.printStackTrace();
117            }
118    
119            classesFile = new File(basedir,"classes");
120            try {
121                classesFile.createNewFile();
122                readClasses();
123            } catch(Exception e) {
124                e.printStackTrace();
125            }
126    
127            oidsFile = new File(basedir,"oids");
128            //try {
129                oidsFile.createNewFile();
130                readOids();
131                /*
132            } catch(Exception e) {
133                e.printStackTrace();
134            }
135                */
136          
137            updateJacNames();
138        }
139    
140        PersistenceAC ac;
141    
142        protected String id;
143        public String getId() {
144            return id;
145        }
146        public void setId(String id) {
147            this.id = id;
148        }
149    
150        protected void updateJacNames() {
151            try {
152                Iterator it = names.entrySet().iterator();
153                Hashtable newNames = new Hashtable();
154                while (it.hasNext()) {
155                    Map.Entry entry = (Map.Entry)it.next();
156                    LongOID oid = (LongOID)entry.getValue();
157                    String name = (String)entry.getKey();
158                    if (name.indexOf('#')==-1) {
159                        String classname = 
160                            Strings.getShortClassName((String)classes.get(oid)).toLowerCase();
161                        if (name.startsWith(classname) && 
162                            name.length()>classname.length() &&
163                            name.charAt(classname.length())!='#') 
164                        {
165                            String newName = classname+"#"+name.substring(classname.length());
166                            it.remove();
167                            newNames.put(newName,oid);
168                            oids.put(oid,newName);
169                        }
170                    }
171                }
172                names.putAll(newNames);
173                writeOids();
174            } catch (Exception e) {
175                logger.error("Failed to update jac names");
176            }
177        }    
178    
179        /**
180         * Safely close the storage. Waits for all operations to terminate.
181         */
182        public void close() {
183            // wait for completion of deleteObject
184            semaphore.acquire();
185            logger.info("FSStorage shutdown hook completed");
186        }
187    
188        /**
189         * Read the "oids" file
190         */
191        void readOids() throws Exception 
192        {
193            oidsFile.createNewFile();
194            StreamTokenizer tokens = getStreamTokenizer(oidsFile);
195    
196            boolean corruptedCounter = false;
197            while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
198                logger.debug("token : "+tokens.sval+"/"+tokens.nval+" "+tokens.ttype);
199                LongOID oid = ac.parseLongOID(tokens.sval,this);
200                long oidval = oid.getOID();
201                if (oidval>lastOID)
202                    lastOID = oidval;
203                tokens.nextToken();
204                logger.debug("token : "+tokens.sval+"/"+tokens.nval+" "+tokens.ttype);
205                String name = tokens.sval;
206                String classid = getClassID(oid);
207                if (names.containsKey(name)) {
208                    logger.error("Corrupted storage! OIDs "+
209                                 names.get(name)+" and "+oid+" have the same name "+name+
210                                 ". Assigning new name to "+oid);
211                    name = newName(classid);
212                    logger.error("  new name is "+name);
213                }
214                oids.put(oid,name);
215                names.put(name,oid);
216    
217                // Ensure counters integrity
218                try {
219                    long count = nameGen.getCounterFromName(name);
220                    long current = nameGen.getCounter(classid);
221                    if (current!=-1 && current <= count) {
222                        logger.error("Corrupted counter for "+classid+". Adjusting "+current+"->"+(count+1));
223                        nameGen.setCounter(classid,count+1);
224                        corruptedCounter = true;
225                    }
226                } catch(Exception e) {
227                }
228            }
229            if (corruptedCounter) {
230                writeNameCounters();
231            }
232        }
233    
234        /**
235         * Writer the "oids" file
236         */ 
237        void writeOids() throws Exception 
238        {
239            logger.debug("writeOids");
240            PrintWriter writer = getPrintWriter(oidsFile,false,false);
241            try {
242                Iterator it = oids.entrySet().iterator();
243                while (it.hasNext()) {
244                    Map.Entry entry = (Map.Entry)it.next();
245                    writer.println(((OID)entry.getKey()).localId()+" "+entry.getValue());         
246                }
247            } finally {
248                writer.close();
249            }
250        }
251    
252        /**
253         * Read the "nameCounters" file
254         */
255        void readNameCounters() throws IOException, FileNotFoundException
256        {
257            nameCountersFile.createNewFile();
258            StreamTokenizer tokens = getStreamTokenizer(nameCountersFile);
259            while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
260                String className = tokens.sval;
261                tokens.nextToken();
262                Long counter = new Long(tokens.sval);
263                logger.debug(className+" -> "+counter);
264                nameGen.put(className,counter);
265            }      
266        }
267    
268        FileDescriptor nameCountersFD;
269        /**
270         * Write the "nameCounters" file
271         */
272        void writeNameCounters() throws IOException {
273            logger.debug("writeNameCounters");
274            PrintWriter writer;
275            FileOutputStream stream = new FileOutputStream(nameCountersFile.toString(),false);
276            writer = getPrintWriter(stream,false);
277            writeMap(nameGen,writer);
278        }
279    
280        public Map getNameCounters() {
281            return nameGen;
282        }
283    
284        public void updateNameCounters(Map counters) throws IOException {
285            nameGen.update(counters);
286            writeNameCounters();
287        }
288    
289        /**
290         * Write the content of a Map to a File, using the toString() of
291         * the keys and values. Does nothing if the map is empty.
292         *
293         * @param map the map
294         * @param file the file 
295         */
296        void writeMapToFile(Map map, File file) throws IOException {
297            if (map.isEmpty()) {
298                file.delete();
299            } else {
300                PrintWriter writer = getPrintWriter(file,false,false);
301                try {
302                    Iterator it = map.entrySet().iterator();
303                    while (it.hasNext()) {
304                        Map.Entry entry = (Map.Entry)it.next();
305                        writer.println(entry.getKey().toString()+" "+entry.getValue());         
306                    }
307                } finally {
308                    writer.close();
309                }
310            }
311        }
312    
313        void writeMap(Map map, PrintWriter writer) throws IOException {
314            try {
315                Iterator it = map.entrySet().iterator();
316                while (it.hasNext()) {
317                    Map.Entry entry = (Map.Entry)it.next();
318                    writer.println(entry.getKey().toString()+" "+entry.getValue());         
319                }
320            } finally {
321                writer.close();
322            }
323        }
324    
325        void readClasses() throws Exception 
326        {
327            logger.debug("readClasses");
328            classesFile.createNewFile();
329            StreamTokenizer tokens = getStreamTokenizer(classesFile);
330            while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
331                LongOID oid = new LongOID(this,Long.parseLong(tokens.sval));
332                long oidval = oid.getOID();
333                if (oidval>lastOID)
334                    lastOID = oidval;
335                tokens.nextToken();
336                String classid = tokens.sval;
337                logger.debug(oid.toString()+" -> "+classid);
338                classes.put(oid,classid);
339            }
340        }
341    
342        void writeClasses() throws Exception 
343        {
344            logger.debug("writeClasses");
345            writeMapToFile(classes,classesFile);
346        }
347    
348        protected StreamTokenizer getStreamTokenizer(File file) 
349            throws FileNotFoundException, IOException
350        {
351            StreamTokenizer tokens = 
352                new StreamTokenizer(
353                    new BufferedReader(
354                        new InputStreamReader(
355                            new FileInputStream(file),
356                            encoding)));
357            tokens.resetSyntax();
358            tokens.wordChars('\000','\377');
359            tokens.whitespaceChars(' ',' ');
360            tokens.whitespaceChars('\t','\t');
361            tokens.whitespaceChars('\n','\n');
362            tokens.whitespaceChars('\r','\r');
363            return tokens;
364        }
365    
366        public OID createObject(String className) throws Exception
367        {
368            semaphore.acquire();
369            PrintWriter writer = null;
370            try {
371                lastOID++;
372                LongOID oid = new LongOID(this,lastOID);
373                classes.put(oid,className);
374                writer = new PrintWriter(getWriter(classesFile,true),true);
375                writer.println(oid.localId()+" "+className);
376                return oid;
377            } finally {
378                if (writer!=null)
379                    writer.close();
380                semaphore.release();
381            }
382        }
383    
384        public void deleteObject(OID oid) throws Exception
385        {
386            semaphore.acquire();
387            try {
388                logger.debug("deleteObject("+oid+")");
389                String name = (String)oids.get(oid);
390                if (name!=null) {
391                    names.remove(name);
392                    oids.remove(oid);
393                    writeOids();
394                } else {
395                    logger.warn("FSStorage.deleteObject: oid "+
396                                   oid+" does not have name");
397                    logger.debug("names = "+classes);
398                }
399                if (classes.containsKey(oid)) {
400                    classes.remove(oid);
401                    writeClasses();
402                } else {
403                    logger.warn("FSStorage.deleteObject: oid "+
404                                   oid+" does not have a class");
405                    logger.debug("classes = "+classes);
406                }
407            } catch(Exception e) {
408                e.printStackTrace();
409            } finally {
410                logger.debug("deleteObject("+oid+") DONE");
411                semaphore.release();
412            }
413            // *** TODO: WE SHOULD ALSO REMOVE COLLECTIONS OWNED BY THE
414            // ***       DELETED OBJECT
415        }
416    
417        public void setField(OID oid, FieldItem field, Object value) 
418            throws Exception
419        {
420            logger.debug("setField("+oid+","+field+","+value+")");
421            Properties props = new Properties();
422            File objectFile = new File(basedir,oid.localId());
423            objectFile.createNewFile();
424            props.load(new FileInputStream(objectFile));
425            props.setProperty(
426                field.getName(),
427                ValueConverter.objectToString(this,value));
428            props.store(new FileOutputStream(objectFile),getClassID(oid)+" "+oid.localId());
429        }
430    
431        public void updateField(OID oid, FieldItem field, Object value) 
432            throws Exception
433        {
434            logger.debug("updateField("+oid+","+field+","+value+")");
435            setField(oid,field,value);
436        }
437    
438        /** oid -> Properties */
439        Hashtable cache = new Hashtable();
440    
441        public Object getField(OID oid, FieldItem field)
442            throws Exception
443        {
444            logger.debug("getField("+oid+","+field+")");
445            Object ret = null;
446            Properties props = (Properties)cache.get(oid);
447            if (props==null) {
448                props = new Properties();
449                File objectFile = new File(basedir,oid.localId());
450                if (objectFile.exists())
451                    props.load(new FileInputStream(objectFile));
452                cache.put(oid,props);
453            }
454            String value = (String)props.get(field.getName());
455            if (value != null) {
456                ret = ValueConverter.stringToObject(this,value);
457            } else {
458                if (field.isPrimitive()) {
459                    logger.warn("no such field in storage "+oid+","+field.getName());
460                }
461                ret = null;
462            }
463            logger.debug(" -> "+ret);
464            return ret;
465        }
466    
467        protected Properties getFields(OID oid) throws IOException
468        {
469            Properties props = new Properties();
470            File f = new File(basedir,oid.localId());
471            if (f.exists())
472                props.load(new FileInputStream(f));
473            return props;
474        }
475    
476        public StorageField[] getFields(OID oid, ClassItem cl, FieldItem[] fields) 
477            throws Exception
478        {
479            Properties props = getFields(oid);
480            StorageField ret[] = new StorageField[fields.length];
481            int count = 0;
482            for (int i=0; i<fields.length; i++) {
483                if (!fields[i].isCalculated() && !fields[i].isTransient()) {
484                    String value = (String)props.get(fields[i].getName());
485                    if (value!=null)
486                        ret[i] = 
487                            new StorageField(
488                                cl,
489                                fields[i],
490                                ValueConverter.stringToObject(this,value));
491                    else
492                        ret[i] = new StorageField(cl,fields[i],null);
493                }
494            }
495            return ret;
496        }
497    
498        // Collection methods
499    
500        protected long getCollectionSize(OID cid) throws Exception
501        {
502            return getList(cid).size();
503        }
504    
505        public OID getCollectionID(OID oid, CollectionItem collection) 
506            throws Exception
507        {
508            return (OID)getField(oid,collection);
509        }
510    
511        // List methods
512    
513        public void clearList(OID cid) throws Exception
514        {
515            saveList(cid,new Vector());
516        }
517    
518        public List getList(OID oid, CollectionItem collection)
519            throws Exception
520        {
521            logger.debug("getList("+oid+","+collection+")");
522            return getList(getCollectionID(oid,collection));
523        }
524    
525        public List getList(OID cid)
526            throws Exception
527        {
528            logger.debug("getList("+cid+")");
529            File file = new File(basedir,cid.localId());
530            Vector ret = new Vector();
531            if (file.exists()) {
532                StreamTokenizer tokens = getStreamTokenizer(file);
533                while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
534                    try {
535                        ret.add(
536                            ValueConverter.stringToObject(
537                                this,
538                                Strings.unslashify(tokens.sval)));
539                    } catch (Throwable e) {
540                        logger.error("failed to list element: "+tokens.sval,e);
541                    }
542                }
543            }
544            logger.debug("getList returns "+ret); 
545            return ret;
546        }
547    
548        public long getListSize(OID cid) throws Exception {
549            logger.debug("getListSize("+cid+")");
550    
551            File file = new File(basedir,cid.localId());
552            long size = 0; 
553            if (file.exists()) {
554                StreamTokenizer tokens = getStreamTokenizer(file);
555                while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
556                    size++;
557                }
558            }
559    
560            return size;
561        }
562    
563        public Object getListItem(OID cid, long index)
564            throws Exception, IndexOutOfBoundsException
565        {
566            logger.debug("getListItem("+cid+","+index+")");
567    
568            File file = new File(basedir,cid.localId());
569            Vector ret = new Vector();
570            long current = 0;
571            if (file.exists()) {
572                StreamTokenizer tokens = getStreamTokenizer(file);
573                while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
574                    if (current==index) {
575                        return 
576                            ValueConverter.stringToObject(
577                                this,Strings.unslashify(tokens.sval));
578                    }
579                    current++;
580                }
581            }
582    
583            throw new IndexOutOfBoundsException(cid+"["+index+"]");
584        }
585    
586        public boolean listContains(OID cid, Object value) 
587            throws Exception
588        {
589            logger.debug("listContains("+cid+","+value+")");
590            return getList(cid).contains(value);
591        }
592    
593        protected void saveList(OID cid, List list) 
594            throws IOException
595        {
596            logger.debug("saveList("+cid+","+list+")");
597            File file = new File(basedir,cid.localId());
598            logger.debug("file = "+file);
599            if (list.isEmpty()) {
600                file.delete();
601            } else {
602                PrintWriter writer = getPrintWriter(file,false,true);
603                try {
604                    for (int i=0; i<list.size(); i++) {
605                        String value = 
606                            ValueConverter.objectToString(this,list.get(i));
607                        logger.debug("  -> "+value);
608                        writer.println(Strings.slashify(value));
609                    }
610                } finally {
611                    writer.close();
612                }
613            }
614        }
615    
616        public void addToList(OID cid, long position, Object value)
617            throws Exception
618        {
619            logger.debug("addToList("+cid+","+position+","+value+")");
620            List list = getList(cid);
621            list.add((int)position,value);
622            saveList(cid,list);
623        }
624    
625        public void addToList(OID cid, Object value)
626            throws Exception
627        {
628            logger.debug("addToList("+cid+","+value+")");
629    
630            PrintWriter writer = getPrintWriter((LongOID)cid,true,true);
631            try {
632                writer.println(
633                    Strings.slashify(ValueConverter.objectToString(this,value)));
634            } finally {
635                writer.close();
636            }
637        }
638    
639        public void setListItem(OID cid, long index, Object value)
640            throws Exception
641        {
642            List list = getList(cid);
643            list.set((int)index,value);
644            saveList(cid,list);
645        }
646    
647        public void removeFromList(OID cid, long position)
648            throws Exception
649        {
650            List list = getList(cid);
651            list.remove((int)position);
652            saveList(cid,list);
653        }
654    
655        public void removeFromList(OID cid, Object value)
656            throws Exception
657        {
658            List list = getList(cid);
659            list.remove(value);
660            saveList(cid,list);
661        }
662    
663        public long getIndexInList(OID cid, Object value)
664            throws Exception
665        {
666            List list = getList(cid);
667            return list.indexOf(value);
668        }
669    
670        public long getLastIndexInList(OID cid, Object value)
671            throws Exception
672        {
673            List list = getList(cid);
674            return list.lastIndexOf(value);
675        }
676    
677    
678        // Set methods
679    
680        public void clearSet(OID cid) throws Exception
681        {
682            saveList(cid,new Vector());
683        }
684    
685        public List getSet(OID oid, CollectionItem collection) 
686            throws Exception 
687        {
688            return getSet(getCollectionID(oid,collection));
689        }
690    
691        public List getSet(OID cid) 
692            throws Exception 
693        {
694            return getList(cid);
695        }
696    
697        public long getSetSize(OID cid) throws Exception {
698            return getCollectionSize(cid);
699        }
700    
701        public boolean addToSet(OID cid, Object value) 
702            throws Exception 
703        {
704            logger.debug("addToSet("+cid+","+value+")");
705            List list = getList(cid);
706            boolean ret = list.add(value);
707            saveList(cid,list);
708            return ret;
709        }
710    
711        public boolean removeFromSet(OID cid, Object value) 
712            throws Exception 
713        {
714            logger.debug("addToSet("+cid+","+value+")");
715            List list = getList(cid);
716            boolean ret = list.remove(value);
717            saveList(cid,list);
718            return ret;
719        }
720    
721        public boolean setContains(OID cid, Object value) 
722            throws Exception
723        {
724            return getList(cid).contains(value);
725        }
726    
727        // Map methods
728    
729        public Map getMap(OID oid, CollectionItem collection) 
730            throws Exception
731        {
732            return getMap(getCollectionID(oid,collection));
733        }
734    
735        public Map getMap(OID cid) 
736            throws Exception
737        {
738            logger.debug("getMap("+cid+")");
739            File file = new File(basedir,cid.localId());
740            Hashtable ret = new Hashtable();
741            if (file.exists()) {
742                StreamTokenizer tokens = getStreamTokenizer(file);
743                while (tokens.nextToken() != StreamTokenizer.TT_EOF) {
744                    Object key = 
745                        ValueConverter.stringToObject(
746                            this,Strings.unslashify(tokens.sval));
747                    tokens.nextToken();
748                    Object value = 
749                        ValueConverter.stringToObject(
750                            this,Strings.unslashify(tokens.sval));
751                    logger.debug("  -> "+key+","+value);
752                    ret.put(key,value);
753                }
754            }
755            return ret;
756        }
757    
758        public long getMapSize(OID cid) throws Exception {
759            return getCollectionSize(cid);
760        }
761    
762        protected void saveMap(OID cid, Map map) 
763            throws IOException
764        {
765            logger.debug("saveMap("+cid+","+map+")");
766            File file = new File(basedir,cid.localId());
767            if (map.isEmpty()) {
768                file.delete();
769            } else {
770                logger.debug("file = "+file);
771                PrintWriter writer = getPrintWriter(file,false,true);
772                try {
773                    Set entrySet = map.entrySet();
774                    Iterator i = entrySet.iterator();
775                    while (i.hasNext()) {
776                        Map.Entry entry = (Map.Entry)i.next();
777                        String value = 
778                            ValueConverter.objectToString(this,entry.getValue());
779                        String key = 
780                            ValueConverter.objectToString(this,entry.getKey());
781                        logger.debug("  -> "+key+","+value);
782                        writer.println(Strings.slashify(key)+" "+Strings.slashify(value));
783                    }
784                } finally {
785                    writer.close();
786                }
787            }
788        }
789    
790        public void clearMap(OID cid) throws Exception
791        {
792            saveMap(cid,new Hashtable());
793        }
794    
795        public Object putInMap(OID cid, Object key, Object value) 
796            throws Exception
797        {
798            Map map = getMap(cid);
799            Object ret = map.put(key,value);
800            saveMap(cid,map);
801            return ret;
802        }
803    
804        public Object getFromMap(OID cid, Object key) 
805            throws Exception
806        {
807            return getMap(cid).get(key);
808        }
809    
810        public boolean mapContainsKey(OID cid, Object key)
811            throws Exception
812        {
813            return getMap(cid).containsKey(key);
814        }
815    
816        public boolean mapContainsValue(OID cid, Object value)
817            throws Exception
818        {
819            return getMap(cid).containsValue(value);
820        }
821    
822        public Object removeFromMap(OID cid, Object key)
823            throws Exception
824        {
825            logger.debug("removeFromMap("+cid+","+key+")");
826            Map map = getMap(cid);
827            Object ret = map.remove(key);
828            saveMap(cid,map);
829            return ret;
830        }
831    
832        // others...
833    
834        public void removeField(OID oid, FieldItem field, Object value) 
835            throws Exception
836        {}
837    
838        public synchronized String newName(String className) throws Exception {
839            semaphore.acquire();
840            try {
841                String name = nameGen.generateName(className);
842                writeNameCounters();
843                return name;
844            } finally {
845                semaphore.release();
846            }
847        }
848    
849        public OID getOIDFromName(String name) throws Exception {
850            return (OID)names.get(name);
851        }
852    
853        public String getNameFromOID(OID oid) throws Exception {
854            return (String)oids.get(oid);
855        }
856    
857        public void bindOIDToName(OID oid,String name) throws Exception
858        {
859            semaphore.acquire();
860            PrintWriter writer = null;
861            try {
862                names.put(name,oid);
863                oids.put(oid,name);
864                writer = getPrintWriter(oidsFile,true,true);
865                writer.println(oid.localId()+" "+name);
866            } finally {
867                if (writer!=null)
868                    writer.close();
869                semaphore.release();
870            }
871        }
872    
873        public void deleteName(String name) throws Exception
874        {}
875    
876        public String getClassID(OID oid) throws Exception {
877            logger.debug("getClassID("+oid+") -> "+(String)classes.get(oid));
878            return (String)classes.get(oid);
879        }
880    
881        public Collection getRootObjects() throws Exception {
882            return oids.keySet();
883        }
884    
885        public Collection getObjects(ClassItem cl) throws Exception {
886            logger.debug("getObjects("+cl+")");
887            if (cl == null) {
888                return classes.keySet();
889            } else {
890                Vector ret = new Vector();
891                getObjects(cl,ret);
892                return ret;
893            }
894        }
895    
896        /**
897         * Gets all instances of a class and its subclasses.
898         * @param cl the class
899         * @param objects instances of the class are added to this collection
900         */
901        protected void getObjects(ClassItem cl, Vector objects) throws Exception {
902            logger.debug("getObjects("+cl+")");
903            Set entries = classes.entrySet();
904            Iterator i = entries.iterator();
905            while(i.hasNext()) {
906                Map.Entry entry = (Map.Entry)i.next();
907                logger.debug("testing "+entry.getValue());
908                if (cl==null || entry.getValue().equals(cl.getName())) {
909                    objects.add(entry.getKey());
910                }
911            }
912            i = cl.getChildren().iterator();
913            while(i.hasNext()) {
914                ClassItem subclass = (ClassItem)i.next();
915                getObjects(subclass,objects);
916            }
917        }
918    
919    
920        public void startTransaction() {
921        }
922    
923        public void commit() {
924        }
925    
926        public void rollback() {
927        }
928    
929        protected PrintWriter getPrintWriter(OID oid, boolean append, boolean autoFlush) 
930            throws IOException 
931        {
932            return getPrintWriter(new File(basedir,oid.localId()),append,autoFlush);
933        }
934    
935        protected PrintWriter getPrintWriter(File file, boolean append, boolean autoFlush) 
936            throws IOException
937        {
938            return
939                new PrintWriter(
940                    // The default buffer size is much too big
941                    new BufferedWriter(getWriter(file,append),1024), 
942                    autoFlush);
943        }
944    
945        protected PrintWriter getPrintWriter(OutputStream stream, boolean autoFlush) 
946            throws IOException
947        {
948            return
949                new PrintWriter(
950                    new BufferedWriter(
951                        new OutputStreamWriter(stream,encoding),
952                        1024), // The default buffer size is much too big
953                    autoFlush);
954        }
955    
956        protected Writer getWriter(File file,  boolean append) throws IOException {
957            return 
958                new OutputStreamWriter(
959                    new FileOutputStream(file.toString(),append),
960                    encoding);
961    
962        }
963    }