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 }