001    package ca.discotek.feenix;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.FileNotFoundException;
006    import java.io.IOException;
007    import java.util.ArrayList;
008    import java.util.List;
009    import java.util.Stack;
010    import java.util.regex.Pattern;
011    
012    import javax.xml.parsers.ParserConfigurationException;
013    import javax.xml.parsers.SAXParser;
014    import javax.xml.parsers.SAXParserFactory;
015    
016    import org.xml.sax.Attributes;
017    import org.xml.sax.SAXException;
018    import org.xml.sax.helpers.DefaultHandler;
019    
020    public class Configuration {
021        
022        public static final String DEFAULT_PROJECT_NAME = "default";
023        
024        private boolean isValidConfiguration = true;
025        private String pathEntries[];
026        private Pattern exclusions[];
027        private String projectName;
028        
029        public Configuration(String pathToFeenixXml) {
030            File file = new File(pathToFeenixXml);
031            if (file.exists()) {
032                try { 
033                    parse(file); 
034                    if (pathEntries.length == 0) {
035                        isValidConfiguration = false;
036                        System.err.println("Feenix Classpath has no entries.");
037                    }
038                    else {
039                        boolean foundInvalidChar = false;
040                        char chars[] = projectName.toCharArray();
041                        for (int i=0; i<chars.length; i++) {
042                            if (Character.isLetterOrDigit(chars[i]) || chars[i] == '-') continue;
043                            else {
044                                foundInvalidChar = true;
045                                break;
046                            }
047                        }
048                        if (foundInvalidChar) {
049                            isValidConfiguration = false;
050                            System.err.println
051                                ("Project name \"" + projectName + "\" is invalid. All project name characters must be alphanumeric or the character '-'. For example \"my-project\" is valid.");
052                        }
053                    }
054                }
055                catch (Exception e) {
056                    isValidConfiguration = false;
057                    System.err.println("Could not parse " + pathToFeenixXml + ". See the following stack trace:");
058                    e.printStackTrace();
059                }
060            }
061            else {
062                isValidConfiguration = false;
063                System.err.println("Configuration file " + pathToFeenixXml + " does not exist.");
064            }
065        }
066        
067        public boolean isValid() {
068            return isValidConfiguration;
069        }
070        
071        public String[] getClasspathEntries() {
072            return pathEntries;
073        }
074        
075        public String getProjectName() {
076            return projectName;
077        }
078        
079        public boolean isExcluded(String name) {
080            for (int i=0; i<exclusions.length; i++) {
081                if (exclusions[i].matcher(name).matches()) 
082                    return true;
083            }
084            return false;
085        }
086        
087        void parse(File configFile) throws ParserConfigurationException, SAXException, IOException {
088            SAXParserFactory factory = SAXParserFactory.newInstance();
089            SAXParser parser = factory.newSAXParser();
090            
091            FileInputStream fis = new FileInputStream(configFile);
092            parser.parse(fis, new ConfigurationHandler());
093        }
094    
095        public static final String FEENIX_CONFIGURATION_ELEMENT = "feenix-configuration";
096        public static final String PROJECT_NAME_ATTRIBUTE = "project-name";
097        public static final String CLASSPATH_ELEMENT = "classpath";
098        public static final String ENTRY_ELEMENT = "entry";
099        public static final String EXCLUDE_ELEMENT = "exclude";
100        
101        class ConfigurationHandler extends DefaultHandler {
102    
103            Stack<String> stack = new Stack<String>();
104            StringBuffer buffer = new StringBuffer();
105            
106            List<String> entryList = new ArrayList<String>();
107            List<String> excludeList = new ArrayList<String>();
108                    
109            
110            public void characters(char[] ch, int start, int length) throws SAXException {
111                buffer.append(ch, start, length);
112            }
113            
114            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
115                buffer.setLength(0);
116                if (qName.equalsIgnoreCase(FEENIX_CONFIGURATION_ELEMENT)) {
117                    String value = attributes.getValue(PROJECT_NAME_ATTRIBUTE);
118                    projectName = value == null ? DEFAULT_PROJECT_NAME : value;
119                }
120                
121                if (qName.equalsIgnoreCase(CLASSPATH_ELEMENT) && !stack.peek().equals(FEENIX_CONFIGURATION_ELEMENT)) {
122                    throw new SAXException
123                        ("Invalid Feenix configuration. <classpath> element should be an immediate child of the <feenix-configuration> element.");
124                }
125                else if (qName.equalsIgnoreCase(ENTRY_ELEMENT) && !stack.peek().equals(CLASSPATH_ELEMENT)) {
126                    throw new SAXException
127                        ("Invalid Feenix configuration. All <entry> elements should be an immediate child of the <classpath> element.");
128                }            
129                else stack.push(qName);
130            }
131            
132            public void endElement(String uri, String localName, String qName) throws SAXException {
133                if (qName.equalsIgnoreCase(ENTRY_ELEMENT)) 
134                    entryList.add(buffer.toString().trim());
135                else if (qName.equalsIgnoreCase(EXCLUDE_ELEMENT)) 
136                    excludeList.add(buffer.toString().trim());
137                
138                stack.pop();
139            }
140            
141            public void endDocument() throws SAXException {
142                pathEntries = entryList.toArray(new String[entryList.size()]);
143                exclusions = new Pattern[excludeList.size()]; 
144                for (int i=0; i<exclusions.length; i++)
145                    exclusions[i] = Pattern.compile(excludeList.get(i));
146            }
147    
148        }
149    }