001    package jcolibri.connector;
002    
003    import java.io.BufferedReader;
004    import java.io.BufferedWriter;
005    import java.io.FileWriter;
006    import java.io.InputStreamReader;
007    import java.net.URL;
008    import java.util.ArrayList;
009    import java.util.Collection;
010    import java.util.Iterator;
011    import java.util.LinkedList;
012    import java.util.List;
013    import java.util.ListIterator;
014    import java.util.StringTokenizer;
015    
016    import javax.xml.parsers.DocumentBuilder;
017    import javax.xml.parsers.DocumentBuilderFactory;
018    
019    import jcolibri.cbrcore.Attribute;
020    import jcolibri.cbrcore.CBRCase;
021    import jcolibri.cbrcore.CaseBaseFilter;
022    import jcolibri.cbrcore.CaseComponent;
023    import jcolibri.cbrcore.Connector;
024    import jcolibri.exception.AttributeAccessException;
025    import jcolibri.exception.InitializingException;
026    import jcolibri.exception.UnImplementedException;
027    import jcolibri.util.FileIO;
028    import jcolibri.connector.plaintextutils.PlainTextTypeConverter;
029    
030    import org.w3c.dom.Document;
031    import org.w3c.dom.Node;
032    import org.w3c.dom.NodeList;
033    
034    /**
035     * <p>
036     * Implements a generic PlainText Connector.
037     * </p>
038     * It manages the persistence of the cases automatically into textual files. Features:
039     * <ul>
040     * <li>By default it only can manage a few data types, although developers can add
041     * their own ones implementing the TypeAdaptor interface.<br>
042     * Supported types and the type extension mechanism is explained in PlainTextTypeConverter.
043     * <li>Only works with one file.
044     * </ul>
045     * <p>
046     * This connector uses the property in the initFromXMLfile() parameter to obtain the
047     * configuration file. This file is a xml that follows the Schema defined in
048     * <a href="PlainTextConnector.xsd">/doc/configfilesSchemas/PlainTextConnector.xsd</a>:<p>
049     * <img src="PlainTextConnectorSchema.jpg">
050     * <p>
051     * This class does not implement any cache mechanims, so cases are read and
052     * written directly. This can be very inefficient in some operations (mainly in
053     * reading)
054     * <p>
055     * Some methods will fail when executing the connector with a case base file inside a jar file.
056     * The retrieve() methods will work properly but the methods that write in the file will fail. 
057     * Extract the file to the file system and run the connector with that location to solve these problems.
058     * <p>
059     * For an example see Test6.
060     * 
061     * @author Juan Antonio Recio García
062     * @version 2.0
063     * @see jcolibri.connector.plaintextutils.PlainTextTypeConverter
064     * @see jcolibri.connector.TypeAdaptor
065     * @see jcolibri.test.test6.Test6
066     */
067    public class PlainTextConnector implements Connector {
068    
069            /* Text file path. */
070            protected String PROP_FILEPATH = "";
071    
072            /* Columns separator. */
073            protected String PROP_DELIM = "";
074        
075            private Class descriptionClass;
076            private Class solutionClass;
077            private Class justOfSolutionClass;
078            private Class resultClass;
079            
080            List<Attribute> descriptionMaps;
081            List<Attribute> solutionMaps;
082            List<Attribute> justOfSolutionMaps;
083            List<Attribute> resultMaps;
084            
085    
086            public void initFromXMLfile(URL file) throws InitializingException {
087                    try {
088                            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
089                            DocumentBuilder db = dbf.newDocumentBuilder();
090                            Document doc = db.parse(file.openStream());
091                            
092                            /** File containing cases */
093                            
094                            this.PROP_FILEPATH = doc.getElementsByTagName("FilePath").item(0).getTextContent();
095                            
096                            /** Text separator */
097                            
098                            this.PROP_DELIM    = doc.getElementsByTagName("Delimiters").item(0).getTextContent();
099                            
100                            /** classes that compose the case*/
101                            
102                            this.descriptionClass = Class.forName(doc.getElementsByTagName("DescriptionClassName").item(0).getTextContent());
103                            
104                            try{
105                                    this.solutionClass =  Class.forName(doc.getElementsByTagName("SolutionClassName").item(0).getTextContent());
106                            }catch(Exception e) {}
107                            
108                            try{
109                                    this.justOfSolutionClass =  Class.forName(doc.getElementsByTagName("JustificationOfSolutionClassName").item(0).getTextContent());
110                            }catch(Exception e) {}
111                            
112                            try{
113                                    this.resultClass =  Class.forName(doc.getElementsByTagName("ResultClassName").item(0).getTextContent());
114                            }catch(Exception e) {}
115                            
116                            
117                            /** Mappings */
118                            
119                            this.descriptionMaps = findMaps(doc.getElementsByTagName("DescriptionMappings").item(0), this.descriptionClass);
120                            
121                            if(this.solutionClass != null)
122                                    this.solutionMaps =  findMaps(doc.getElementsByTagName("SolutionMappings").item(0), this.solutionClass);
123                            
124                            if(this.justOfSolutionClass != null)
125                                    this.justOfSolutionMaps = findMaps(doc.getElementsByTagName("JustificationOfSolutionMappings").item(0), this.justOfSolutionClass);
126                            
127                            if(this.resultClass != null)
128                                    this.resultMaps = findMaps(doc.getElementsByTagName("ResultMappings").item(0), this.resultClass);
129                            
130                    }catch(Exception e){
131                            throw new InitializingException(e);
132                    }
133                    
134    
135            }
136    
137            private List<Attribute> findMaps(Node n, Class _class)
138            {
139                    List<Attribute> res = new ArrayList<Attribute>();
140                    NodeList childs = n.getChildNodes();
141                    for(int i=0; i<childs.getLength(); i++)
142                    {
143                            Node c = childs.item(i);
144                            if(c.getNodeName().equals("Map")){
145                                    String attributeName = c.getTextContent();
146                                    res.add(new Attribute(attributeName, _class));
147                            }
148                    }
149                    return res;
150            }
151            
152    
153            public void close() {
154                    //does nothing
155            }
156    
157            /**
158             * Stores the cases in the data base. Note that this method does not control
159             * that the case name (== primary key) is repeated, so developers must be
160             * careful with this.
161             * 
162             * @param cases
163             *            Cases to store.
164             * @throws UnImplementedException 
165             */
166            public void storeCases(Collection<CBRCase> cases)
167            {
168                    try {
169                            BufferedWriter br = null;
170                            br = new BufferedWriter(new FileWriter(FileIO.findFile(this.PROP_FILEPATH).getFile(), true));
171                            if (br == null)
172                                    throw new Exception("Error opening file for writing: "+ this.PROP_FILEPATH);
173    
174                            char separator = this.PROP_DELIM.charAt(0);
175                            
176                            for (CBRCase _case : cases) {
177                                    br.newLine();
178                                    StringBuffer line = new StringBuffer();
179                                    
180                                    CaseComponent description = _case.getDescription();
181                                    writeComponent(description, this.descriptionMaps, line, separator, true);
182                                    
183                                    CaseComponent solution = _case.getSolution();
184                                    if(solution!=null)
185                                    {
186                                            line.append(separator);
187                                            writeComponent(solution, this.solutionMaps,  line, separator, false);
188                                    }
189                                    
190                                    CaseComponent justOfSolution = _case.getJustificationOfSolution();
191                                    if(justOfSolution!=null)
192                                    {
193                                            line.append(separator);
194                                            writeComponent(justOfSolution, this.justOfSolutionMaps,  line, separator, false);
195                                    }
196                                    
197                                    CaseComponent result = _case.getResult();
198                                    if(result!=null)
199                                    {
200                                            line.append(separator);
201                                            writeComponent(result, this.resultMaps, line, separator, false);
202                                    }
203    
204                                    br.write(line.toString());
205                            }
206                            br.close();
207                    } catch (Exception e) {
208                            org.apache.commons.logging.LogFactory.getLog(this.getClass()).error(e);
209                    }
210            }
211            
212            private void writeComponent(CaseComponent comp, List<Attribute> maps, StringBuffer line, char separator, boolean includeId)
213            {
214                    try {
215                            if(includeId)
216                                    line.append(comp.getIdAttribute().getValue(comp));
217                            for(Attribute a: maps)
218                            {
219                                    line.append(separator);
220                                    line.append(a.getValue(comp));
221                            }
222                    } catch (AttributeAccessException e) {
223                            org.apache.commons.logging.LogFactory.getLog(this.getClass()).error(e);
224                    }
225            }
226    
227            /**
228             * Deletes cases from the case base. It only uses the case name (primary
229             * key) to remove the row. Note that this method is very inefficient because
230             * it reads all the database, removes the rows in memory, and writes it
231             * again into the text file.
232             * 
233             * @param cases
234             *            Cases to delete
235             */
236            public void deleteCases(Collection<CBRCase> cases){
237                    try {
238                            BufferedReader br = null;
239                            br = new BufferedReader( new InputStreamReader(FileIO.findFile(this.PROP_FILEPATH).openStream()));
240                            if (br == null)
241                                    throw new Exception("Error opening file for reading: "
242                                                    + this.PROP_FILEPATH);
243    
244                            ArrayList<String> lines = new ArrayList<String>();
245                            String line = "";
246                            while ((line = br.readLine()) != null) {
247                                    if (line.startsWith("#") || (line.length() == 0)) {
248                                            lines.add(line);
249                                            continue;
250                                    }
251    
252                                    StringTokenizer st = new StringTokenizer(line, this.PROP_DELIM);
253                                    String caseId = st.nextToken();
254                                    for (Iterator cIter = cases.iterator(); cIter.hasNext();) {
255                                            CBRCase _case = (CBRCase) cIter.next();
256                                            if (!caseId.equals(_case.getID().toString()))
257                                                    lines.add(line);
258                                    }
259                            }
260                            br.close();
261    
262                            BufferedWriter bw = null;
263                            bw = new BufferedWriter(new FileWriter(FileIO.findFile(this.PROP_FILEPATH).getFile(), false));
264                            if (bw == null)
265                                    throw new Exception("Error opening file for writing: "
266                                                    + this.PROP_FILEPATH);
267                            for (ListIterator lIter = lines.listIterator(); lIter.hasNext();) {
268                                    line = (String) lIter.next();
269                                    bw.write(line);
270                                    bw.newLine();
271                            }
272                            bw.close();
273    
274                    } catch (Exception e) {
275                            org.apache.commons.logging.LogFactory.getLog(this.getClass()).error(
276                                            "Error deleting cases " + e.getMessage());
277                    }
278            }
279    
280            /**
281             * Retrieves all cases from the text file. It maps data types using the
282             * PlainTextTypeConverter class.
283             * 
284             * @return Retrieved cases.
285             */
286            public Collection<CBRCase> retrieveAllCases() {
287                    LinkedList<CBRCase> cases = new LinkedList<CBRCase>();
288                    try {
289                            BufferedReader br = null;
290                            br = new BufferedReader( new InputStreamReader(FileIO.openFile(this.PROP_FILEPATH)));
291                            if (br == null)
292                                    throw new Exception("Error opening file: " + this.PROP_FILEPATH);
293    
294                            String line = "";
295                            while ((line = br.readLine()) != null) {
296    
297                                    if (line.startsWith("#") || (line.length() == 0))
298                                            continue;
299                                    StringTokenizer st = new StringTokenizer(line, this.PROP_DELIM);
300                                    
301                                    CBRCase _case = new CBRCase();
302                                    
303                                    CaseComponent description = (CaseComponent)this.descriptionClass.newInstance();
304                                    fillComponent(description, st, this.descriptionMaps, true);
305                                    _case.setDescription(description);
306                                    
307                                    if(this.solutionClass != null)
308                                    {
309                                            CaseComponent solution = (CaseComponent)this.solutionClass.newInstance();
310                                            fillComponent(solution, st, this.solutionMaps, false);
311                                            _case.setSolution(solution);
312                                    }
313                                    if(this.justOfSolutionClass != null)
314                                    {
315                                            CaseComponent justificationOfSolution = (CaseComponent)this.justOfSolutionClass.newInstance();
316                                            fillComponent(justificationOfSolution, st, this.justOfSolutionMaps, false);
317                                            _case.setJustificationOfSolution(justificationOfSolution);
318                                    }
319                                    if(this.resultClass != null)
320                                    {
321                                            CaseComponent result = (CaseComponent)this.resultClass.newInstance();
322                                            fillComponent(result, st, this.resultMaps, false);
323                                            _case.setResult(result);
324                                    }
325                                    
326                                    cases.add(_case);
327                            }
328                            br.close();
329                    } catch (Exception e) {
330                            org.apache.commons.logging.LogFactory.getLog(this.getClass()).error(
331                                            "Error retrieving cases " + e.getMessage());
332                    }
333                    return cases;
334            }
335    
336    
337            private void fillComponent(CaseComponent component, StringTokenizer st, List<Attribute> maps, boolean includeId)
338            {
339                    try {
340                            Class type;
341                            Object value;
342                            
343                            if(includeId)
344                            {
345                                    Attribute idAttribute = component.getIdAttribute();
346                                    type = idAttribute.getType();
347                                    value = PlainTextTypeConverter.convert(st.nextToken(), type);
348                                    idAttribute.setValue(component, value);
349                            }
350                            
351                            for(Attribute at : maps)
352                            {
353                                    type = at.getType();
354                                    value = PlainTextTypeConverter.convert(st.nextToken(), type);
355                                    at.setValue(component, value);
356                            }
357                            
358                    } catch (Exception e) {
359                            org.apache.commons.logging.LogFactory.getLog(this.getClass()).error(
360                                            "Error creating case: " + e.getMessage());
361                    }
362            }
363            
364            
365            public Collection<CBRCase> retrieveSomeCases(CaseBaseFilter filter) {
366                    // TODO Auto-generated method stub
367                    return null;
368            }
369    
370    
371    
372    }