001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.io;
031
032import java.io.File;
033import java.io.IOException;
034import java.io.PrintWriter;
035import java.util.Scanner;
036
037
038import cern.colt.Arrays;
039
040
041/**
042 * In its simplest form this function constructs an object using the construct found for the objects
043 * specified. This works with all java classes. If the class also happens to be cachable, the property
044 * org.openimaj.cache.dir is use (or $HOME/.OIcache) to save the class. The object instance is saved in
045 * $CACHE_DIR/classpackage/classname/unique-name where unique-name is taken from cachable. 
046 * 
047 * If cachable objects already exist they are read in using {@link IOUtils#read(java.io.File)}.
048 * 
049 * Once created, cachable objects are saved using either {@link IOUtils#writeASCII(java.io.File, WriteableASCII)} or
050 * {@link IOUtils#writeBinary(java.io.File, WriteableBinary)}
051 * 
052 * 
053 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
054 *
055 */
056public class Cache {
057        
058        private final static String cacheProp = "org.openimaj.cache.dir";
059        private static final String CACHE_DIR_NAME = ".OIcache";
060        
061        /**
062         * load an instance using {@link #load(Object, Class, boolean)} with class as instance#getClass. The cache lookup is not
063         * skipped. 
064         * @param <T>
065         * @param instance
066         * @return a loaded instance
067         */
068        public static <T> T load(T instance){
069                @SuppressWarnings("unchecked")
070                T ret = (T) load(instance,instance.getClass(),false);
071                return ret; 
072        }
073        
074        /**
075         * Create an instance of the clazz for the objects (for the constructor).
076         * If the class creates {@link Cachable} instances, an attempt is made to load
077         * the instance from the Cache. 
078         * 
079         * @param <T> The type of the object returned
080         * @param clazz the class which to get a cached instance of
081         * @param objects the parameters used to instantiate and index the cached object
082         * @return an instance of the clazz 
083         */
084        public static <T> T load(Class<? extends T> clazz, Object ... objects ) {
085                return load(clazz,false,objects);
086        }
087        
088        /**
089         * Create an instance of the clazz for the objects (for the constructor).
090         * If the class creates {@link Cachable} instances, an attempt is made to load
091         * the instance from the Cache. 
092         * 
093         * @param <T> The type of the object returned
094         * @param clazz the class which to get a cached instance of
095         * @param objects the parameters used to instantiate and index the cached object
096         * @return an instance of the clazz 
097         */
098        public static <T> T loadSkipCache(Class<? extends T> clazz, Object ... objects ) {
099                return load(clazz,true,objects);
100        }
101        
102        /**
103         * Clear the cache entry for a given clazz and a set of constructors
104         * @param <T>
105         * @param clazz
106         * @param objects
107         */
108        public static <T extends Cachable> void clear(Class<T> clazz, Object ... objects) {
109                T instance = createInstance(clazz,objects);
110                File location = constructCachedLocation(instance, clazz);
111                FileUtils.deleteRecursive(location);
112        }
113        
114        /**
115         * Clear the cache entry for a given cachable object
116         * @param instance
117         */
118        public static void clear(Cachable instance) {
119                File location = constructCachedLocation(instance, instance.getClass());
120                FileUtils.deleteRecursive(location);
121        }
122        
123        /**
124         * @param <T> The type of the object returned
125         * @param clazz the class which to get a cached instance of
126         * @param skipcache force the cache to be skipped, for a new instance to be returned 
127         * @param objects the parameters used to instantiate and index the cached object
128         * @return an instance of the clazz
129         */
130        private static <T> T load(Class<? extends T> clazz, boolean skipcache, Object ... objects ) {
131                T instance = createInstance(clazz,objects);
132                return load(instance,clazz,skipcache);
133        }
134        
135        /**
136         * @param <T> The type of the object returned
137         * @param instance the instance to attempt to load from the cache if the instance is {@link Cachable}
138         * @param clazz the class which to get a cached instance of
139         * @param skipcache force the cache to be skipped, for a new instance to be returned
140         * @return an instance of the clazz
141         */
142        @SuppressWarnings("unchecked")
143        public static <T> T load(T instance, Class<? extends T> clazz, boolean skipcache) {
144                if(instance instanceof Cachable && !skipcache){
145                        Cachable cinstance = (Cachable)instance;
146                        File cachedLocation = constructCachedLocation(cinstance,clazz);
147                        try{
148                                // if it exists load it!
149                                if(cachedLocation.exists()){
150                                        if(cinstance instanceof ReadWriteableASCII){
151                                                ReadWriteableASCII rwinstance = cacheFromASCII((ReadWriteableASCII)cinstance,cachedLocation);
152                                                instance = (T) rwinstance;
153                                        }
154                                        else if(cinstance instanceof ReadWriteableBinary){
155                                                ReadWriteableBinary rwinstance = cacheFromBinary((ReadWriteableBinary)cinstance,cachedLocation);
156                                                instance = (T) rwinstance;
157                                        }
158                                }
159                                // if not, create the parent directory and save it!
160                                else{
161                                        if(!cachedLocation.getParentFile().exists() && !cachedLocation.getParentFile().mkdirs()){
162                                                throw new IOException("Couldn't create cache directory!");
163                                        }
164                                        if(cinstance instanceof ReadWriteableASCII){
165                                                IOUtils.writeASCII(cachedLocation, (ReadWriteableASCII) instance);
166                                        }
167                                        else if(cinstance instanceof ReadWriteableBinary){
168                                                IOUtils.writeBinary(cachedLocation, (ReadWriteableBinary) instance);
169                                        }
170                                }
171                                
172                        }
173                        catch(Exception e){
174                                e.printStackTrace();
175                                System.err.println("Error reading or writing object from cache");
176                        }
177                }
178                return instance;
179        }
180        
181        private static File constructCachedLocation(Cachable cinstance, Class<?> clazz) {
182                String cdir = System.getProperty(cacheProp);
183                if(cdir == null || cdir.equals(""))
184                        cdir = System.getProperty("user.home") + "/" + CACHE_DIR_NAME;
185                
186                String pname = clazz.getPackage().getName();
187                String cname = clazz.getName();
188                String uname = cinstance.identifier();
189                File cachedLocation = new File(cdir,String.format("%s/%s/%s",pname,cname,uname));
190                return cachedLocation;
191        }
192
193        private static <T> T createInstance(Class<T> clazz, Object ... objects) {
194                Class<?>[] classes = new Class<?>[objects.length];
195                int i = 0;
196                for (Object object : objects) {
197                        classes[i++] = object.getClass();
198                }
199                T instance = null;
200                try {
201                        instance = clazz.getConstructor(classes).newInstance(objects);
202                } catch (Exception e) {
203                        System.err.format("Error finding constructor for class %s with classes %s\n",clazz.toString(),Arrays.toString(classes));
204                }
205                return instance;
206        }
207
208        @SuppressWarnings("unchecked")
209        private static <T extends ReadWriteableASCII> T cacheFromASCII(T rwascii,File f) throws IOException {
210                return IOUtils.read(f, (Class<T>)rwascii.getClass());
211        }
212        
213        @SuppressWarnings("unchecked")
214        private static <T extends ReadWriteableBinary > T cacheFromBinary(T rwbin,File f) throws IOException {
215                return IOUtils.read(f, (Class<T>)rwbin.getClass());
216        }
217        
218        static class CachableStringInteger implements CachableASCII{
219                int integer;
220                String string;
221                
222                public CachableStringInteger(){
223                        
224                }
225                
226                public CachableStringInteger(Integer integer, String string) {
227                        this.integer = integer;
228                        this.string = string;
229                }
230                
231                @Override
232                public void readASCII(Scanner in) throws IOException {
233                        this.integer = Integer.parseInt(in.nextLine());
234                        this.string = in.nextLine();
235                }
236
237                @Override
238                public String asciiHeader() {
239                        return "STRING_INT\n";
240                }
241
242                @Override
243                public void writeASCII(PrintWriter out) throws IOException {
244                        out.println(integer);
245                        out.println(string);
246                }
247
248                @Override
249                public String identifier() {
250                        return String.format("%d-%s",integer,string);
251                }
252                
253                @Override
254                public String toString() {
255                        return identifier();
256                }
257        }
258        
259        /**
260         * @param args
261         */
262        public static void main(String[] args) {
263                String a = load(String.class,"wang");
264                System.out.println("This is the created string: " + a);
265                CachableStringInteger csi = load(CachableStringInteger.class,1,"wang");
266                System.out.println("Got this from the cache.create():" + csi);
267        }
268        
269}