001    package ca.discotek.feenix;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.IOException;
006    import java.lang.reflect.Method;
007    import java.util.HashMap;
008    import java.util.Map;
009    import java.util.regex.Pattern;
010    
011    import ca.discotek.feenix.asm.ImplementationGenerator;
012    import ca.discotek.feenix.asm.InterfaceGenerator;
013    import ca.discotek.feenix.asm.ModifyClassVisitor;
014    import ca.discotek.feenix.classpath.DirectoryEntry;
015    import ca.discotek.feenix.classpath.EarEntry;
016    import ca.discotek.feenix.classpath.PathDatabase;
017    import ca.discotek.feenix.classpath.WarEntry;
018    import ca.discotek.feenix.classpath.ZipEntry;
019    import ca.discotek.feenix.io.IOUtil;
020    import ca.discotek.rebundled.org.objectweb.asm.ClassReader;
021    import ca.discotek.rebundled.org.objectweb.asm.ClassWriter;
022    
023    public class ClassManager {
024        
025        public static final String CONFIG_PATH = "feenix-config";
026        static PathDatabase db;
027        
028        static final Configuration configuration;
029        
030        static {
031            String configPath = System.getProperty(CONFIG_PATH);
032            if (configPath == null)
033                throw new IllegalStateException("Path to feenix configuration file not specified. Use " + CONFIG_PATH + " system property.");
034            else {
035                File file = new File(configPath);
036                if (!file.exists())
037                    throw new IllegalStateException("Specified configuration file \"" + configPath + "\" does not exist.");
038                else {
039                    
040                }
041            }
042            configuration = new Configuration(configPath);
043            
044            db = new PathDatabase(configuration.getProjectName());
045            String entries[] = configuration.getClasspathEntries();
046            File entryFile;
047            for (int i=0; i<entries.length; i++) {
048                entryFile = new File(entries[i]);
049                if (entryFile.exists()) {
050                    if (entries[i].endsWith(".jar")) {
051                        try { db.addZipEntry(new ZipEntry(entryFile)); }
052                        catch (IOException e) {
053                            System.err.println("Unable to to add configuration entry " + entries[i] + " as jar file.");
054                        } 
055                    }
056                    else if (entries[i].endsWith(".war")) {
057                        try { db.addWarEntry(new WarEntry(entryFile)); }
058                        catch (IOException e) {
059                            System.err.println("Unable to to add configuration entry " + entries[i] + " as war file.");
060                        } 
061                    }
062                    else if (entries[i].endsWith(".ear")) {
063                        try { db.addEarEntry(new EarEntry(entryFile)); }
064                        catch (IOException e) {
065                            System.err.println("Unable to to add configuration entry " + entries[i] + " as ear file.");
066                        } 
067                    }
068                    else if (entryFile.isDirectory()) {
069                        db.addDirectoryEntry(new DirectoryEntry(entryFile));
070                    }
071                }
072                else {
073                    //log properly later.
074                    System.err.println("Configuration entry " + entries[i] + " does not exist.");
075                }
076            }
077        }
078        
079        public static final String INTERFACE_SUFFIX = "__interface__";
080        public static final String IMPLEMENTATION_SUFFIX = "__impl__";
081        
082        public static final Pattern IMPLEMENTATION_SUFFIX_PATTERN = 
083            Pattern.compile(".*" + IMPLEMENTATION_SUFFIX + "[0-9]*");
084        
085        public static boolean isInterface(String className) {
086            return className.endsWith(INTERFACE_SUFFIX);
087        }
088        
089        public static String trimInterfaceSuffix(String interfaceName) {
090            return interfaceName.substring(0, interfaceName.length() - INTERFACE_SUFFIX.length());
091        }
092        
093        public static boolean isImplementation(String className) {
094            return IMPLEMENTATION_SUFFIX_PATTERN.matcher(className).matches();
095        }
096        
097        public static String trimImplementationSuffix(String className) {
098            int index = className.lastIndexOf(IMPLEMENTATION_SUFFIX);
099            return className.substring(0, index);
100        }
101    
102        public static String getInterfaceName(String rootClassName) {
103            return rootClassName + INTERFACE_SUFFIX;
104        }
105        
106        public static String getImplementationName(String rootClassName) {
107            return rootClassName + IMPLEMENTATION_SUFFIX_PATTERN;
108        }
109        
110        public static Configuration getConfiguration() {
111            return configuration;
112        }
113        
114        public static byte[] getClassBytes(String slashClassName) {
115            if (isInterface(slashClassName)) 
116                return InterfaceGenerator.generate(slashClassName, trimInterfaceSuffix(slashClassName));
117            else if (isImplementation(slashClassName)) {
118                String rootClassName = trimImplementationSuffix(slashClassName);
119                File file = db.getFile(rootClassName.replace('.', '/') + ".class");
120                if (file != null) 
121                    return ImplementationGenerator.generate(slashClassName, file);
122            }
123            else {
124                File file = db.getFile(slashClassName + ".class");
125                if (file != null) 
126                    return ModifyClassVisitor.generate(slashClassName, file);
127            }
128            
129            return null;
130        }
131        
132        static Map<String, Long> classTimestampMap = new HashMap<String, Long>();
133        
134        static boolean isOutDated(String className, long timestamp) {
135            Long l = classTimestampMap.get(className);
136            if (l == null) {
137                classTimestampMap.put(className, timestamp);
138                return false;
139            }
140            else {
141                classTimestampMap.put(className, timestamp);
142                return timestamp > l;
143            }
144        }
145        
146        static Map<String, Integer> classVersionMap = new HashMap<String, Integer>();
147        
148        static int getNextVersion(String className) {
149            Integer value = classVersionMap.get(className);
150            if (value == null) 
151                value = -1;
152            value++;
153            classVersionMap.put(className, value);
154            return value;
155        }
156        
157        public static Object getUpdate(Class type) {
158            String dotClassName = type.getName();
159            String slashClassName = dotClassName.replace('.', '/');
160            
161            File file = db.getFile(slashClassName + ".class");
162            if (file != null && file.isFile()) {
163                long lastModified = file.lastModified();
164                if (isOutDated(dotClassName, lastModified)) {
165                    String newName = slashClassName + IMPLEMENTATION_SUFFIX + getNextVersion(slashClassName);
166                    byte bytes[] = getClassBytes(newName);
167                    try { 
168                        Method method = ClassLoader.class.getDeclaredMethod("defineMyClass", new Class[]{String.class, byte[].class});
169                        Class newType = (Class) method.invoke(type.getClassLoader(), new Object[]{newName.replace('/', '.'), bytes});
170                        return newType.newInstance();
171                    }
172                    catch (Exception e) {
173                        e.printStackTrace();
174                    }
175                }
176            }
177            
178            return null;
179        }
180    }