Coverage Report - org.webslinger.commons.vfs.handlers.cow.COWStateXml
 
Classes in this File Line Coverage Branch Coverage Complexity
COWStateXml
90%
80/89
100%
11/11
0
COWStateXml$Entry
100%
5/5
N/A
0
COWStateXml$State
99%
71/72
100%
23/23
0
 
 1  
 package org.webslinger.commons.vfs.handlers.cow;
 2  
 
 3  
 import java.io.InputStream;
 4  
 import java.io.IOException;
 5  
 import java.io.OutputStream;
 6  
 import java.util.ArrayList;
 7  
 import java.util.Arrays;
 8  
 import java.util.Collection;
 9  
 import java.util.HashMap;
 10  
 import java.util.HashSet;
 11  
 import java.util.LinkedHashMap;
 12  
 import java.util.Map;
 13  
 import java.util.Queue;
 14  
 import java.util.concurrent.ConcurrentHashMap;
 15  
 import java.util.concurrent.atomic.AtomicReference;
 16  
 import javax.xml.parsers.DocumentBuilder;
 17  
 import javax.xml.parsers.DocumentBuilderFactory;
 18  
 import javax.xml.parsers.ParserConfigurationException;
 19  
 import javax.xml.transform.OutputKeys;
 20  
 import javax.xml.transform.Transformer;
 21  
 import javax.xml.transform.TransformerFactory;
 22  
 import javax.xml.transform.dom.DOMSource;
 23  
 import javax.xml.transform.stream.StreamResult;
 24  
 
 25  
 import org.w3c.dom.Document;
 26  
 import org.w3c.dom.Element;
 27  
 import org.w3c.dom.Node;
 28  
 import org.xml.sax.SAXException;
 29  
 import org.xml.sax.SAXParseException;
 30  
 
 31  
 import org.apache.commons.vfs.FileName;
 32  
 import org.apache.commons.vfs.FileObject;
 33  
 import org.apache.commons.vfs.FileSystem;
 34  
 import org.apache.commons.vfs.FileSystemException;
 35  
 
 36  
 import org.webslinger.commons.vfs.VFSUtil;
 37  
 import org.webslinger.lang.ConcurrentCache;
 38  
 import org.webslinger.util.GeneratedResult;
 39  
 import org.webslinger.xml.XmlUtil;
 40  
 
 41  
 public class COWStateXml {
 42  
     private static final Queue<DocumentBuilder> xmlBuilders;
 43  
 
 44  
     static {
 45  1
         DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
 46  1
         builderFactory.setCoalescing(true);
 47  1
         builderFactory.setExpandEntityReferences(true);
 48  1
         builderFactory.setIgnoringComments(true);
 49  1
         builderFactory.setIgnoringElementContentWhitespace(true);
 50  1
         builderFactory.setNamespaceAware(false);
 51  1
         builderFactory.setValidating(false);
 52  
         //builderFactory.setXIncludeAware(false);
 53  
         try {
 54  1
             xmlBuilders = XmlUtil.createPool(builderFactory, 20);
 55  0
         } catch (RuntimeException e) {
 56  0
             throw e;
 57  0
         } catch (Exception e) {
 58  0
             throw (InternalError) new InternalError("loading xmlBuilders").initCause(e);
 59  1
         }
 60  1
     }
 61  
 
 62  
     private final FileSystem fileSystem;
 63  
     private final FileName fileName;
 64  
     private Document doc;
 65  72
     private final AtomicReference<State> state = new AtomicReference<State>();
 66  
 
 67  72
     public COWStateXml(FileObject file) throws IOException {
 68  72
         this.fileName = file.getName();
 69  72
         this.fileSystem = file.getFileSystem();
 70  72
         state.set(load(file));
 71  72
     }
 72  
 
 73  
     protected static HashMap<String, Entry> parse(Document doc) {
 74  249
         HashMap<String, Entry> entries = new HashMap<String, Entry>();
 75  249
         Node node = doc.getDocumentElement().getFirstChild();
 76  640
         while (node != null) {
 77  391
             if (node.getNodeType() == Node.ELEMENT_NODE && "item".equals(node.getNodeName())) {
 78  197
                 Element entryElement = (Element) node;
 79  197
                 boolean isDeleted = "true".equals(entryElement.getAttribute("deleted"));
 80  197
                 LinkedHashMap<String, Element> bases = new LinkedHashMap<String, Element>();
 81  197
                 Node baseNode = node.getFirstChild();
 82  541
                 while (baseNode != null) {
 83  344
                     if (baseNode.getNodeType() == Node.ELEMENT_NODE && "base".equals(baseNode.getNodeName())) {
 84  168
                         Element baseElement = (Element) baseNode;
 85  168
                         String base = baseElement.getAttribute("target");
 86  168
                         Element oldElement = bases.put(base, baseElement);
 87  168
                         if (oldElement != null) entryElement.removeChild(oldElement);
 88  
                     }
 89  344
                     baseNode = baseNode.getNextSibling();
 90  
                 }
 91  197
                 Entry newEntry = new Entry(entryElement, bases, isDeleted);
 92  197
                 Entry oldEntry = entries.put(newEntry.element.getAttribute("name"), newEntry);
 93  197
                 if (oldEntry != null) node.getParentNode().removeChild(oldEntry.element);
 94  
             }
 95  391
             node = node.getNextSibling();
 96  
         }
 97  249
         return entries;
 98  
     }
 99  
 
 100  
     protected static State load(FileObject file) throws FileSystemException {
 101  
         long lastModified;
 102  
         HashMap<String, Entry> entries;
 103  
         Document doc;
 104  72
         DocumentBuilder documentBuilder = xmlBuilders.poll();
 105  
         try {
 106  72
             if (!file.exists()) {
 107  17
                 doc = documentBuilder.newDocument();
 108  17
                 doc.appendChild(doc.createElement("state"));
 109  17
                 lastModified = -1;
 110  17
                 entries = new HashMap<String, Entry>();
 111  
             } else {
 112  55
                 InputStream in = file.getContent().getInputStream();
 113  55
                 lastModified = file.getContent().getLastModifiedTime();
 114  55
                 doc = documentBuilder.parse(in);
 115  55
                 entries = parse(doc);
 116  55
                 in.close();
 117  
             }
 118  0
         } catch (Exception e) {
 119  0
             throw VFSUtil.makeFileSystemException(e);
 120  
         } finally {
 121  72
             xmlBuilders.offer(documentBuilder);
 122  72
         }
 123  72
         return new State(doc, entries, lastModified);
 124  
     }
 125  
 
 126  
     protected static void save(FileObject file, State state) throws FileSystemException {
 127  194
         DocumentBuilder documentBuilder = xmlBuilders.element();
 128  
         try {
 129  194
             if (!state.entries.isEmpty()) {
 130  136
                 OutputStream out = file.getContent().getOutputStream();
 131  136
                 DOMSource source = new DOMSource(state.doc);
 132  136
                 StreamResult result = new StreamResult(out);
 133  136
                 TransformerFactory factory = TransformerFactory.newInstance();
 134  136
                 Transformer transformer = factory.newTransformer();
 135  136
                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
 136  136
                 transformer.transform(source, result);
 137  136
                 out.close();
 138  136
             } else {
 139  58
                 file.delete();
 140  
             }
 141  0
         } catch (Exception e) {
 142  0
             throw VFSUtil.makeFileSystemException(e);
 143  
         } finally {
 144  194
             xmlBuilders.offer(documentBuilder);
 145  194
         }
 146  194
     }
 147  
 
 148  
     protected FileObject getFile() throws FileSystemException {
 149  194
         return fileSystem.resolveFile(fileName);
 150  
     }
 151  
 
 152  
     public void removeDeletedChildren(Collection<String> names) throws FileSystemException {
 153  62
         state.get().removeDeletedChildren(names);
 154  62
     }
 155  
 
 156  
     public Collection<String> loadData(String name, boolean[] isDeleted) throws FileSystemException {
 157  682
         return state.get().loadData(name, isDeleted);
 158  
     }
 159  
 
 160  
     public boolean submitRequests(String name, COWStorageHandler.Request... requests) throws FileSystemException {
 161  194
         boolean[] saved = new boolean[1];
 162  
         State oldState, newState;
 163  
         do {
 164  194
             oldState = state.get();
 165  194
             newState = oldState.submitRequests(name, requests);
 166  194
             if (oldState == newState) return false;
 167  194
             if (newState == null) {
 168  58
                 Document doc = (Document) oldState.doc.cloneNode(false);
 169  58
                 doc.appendChild(doc.createElement("state"));
 170  58
                 newState = new State(doc, new HashMap<String, Entry>(), -1);
 171  
             }
 172  194
         } while (!state.compareAndSet(oldState, newState));
 173  194
         save(getFile(), newState);
 174  194
         return true;
 175  
     }
 176  
 
 177  
     public String toString() {
 178  0
         return "COWStateXml(" + fileName.toString() + ")";
 179  
     }
 180  
 
 181  
     protected final static class State {
 182  
         protected final Document doc;
 183  
         protected final HashMap<String, Entry> entries;
 184  
         protected final long lastModified;
 185  
 
 186  324
         protected State(Document doc, HashMap<String, Entry> entries, long lastModified) {
 187  324
             this.doc = doc;
 188  324
             this.entries = entries;
 189  324
             this.lastModified = lastModified;
 190  324
         }
 191  
 
 192  
         private State copy(boolean doCopy) {
 193  379
             if (!doCopy) return this;
 194  194
             Document newDoc = (Document) doc.cloneNode(true);
 195  194
             State newState = new State(newDoc, parse(newDoc), lastModified);
 196  194
             return newState;
 197  
         }
 198  
 
 199  
         private State checkEmpty(String name) {
 200  67
             Entry entry = entries.get(name);
 201  67
             if (entry != null && !entry.isDeleted && entry.bases.isEmpty()) {
 202  14
                 entries.remove(name);
 203  14
                 entry.element.getParentNode().removeChild(entry.element);
 204  
             }
 205  67
             return entries.isEmpty() ? null : this;
 206  
         }
 207  
 
 208  
         private Entry newEntry(String name, LinkedHashMap<String, Element> bases, boolean isDeleted) {
 209  138
             if (".cowstate.xml".equals(name)) new Exception().printStackTrace();
 210  138
             Element element = doc.createElement("item");
 211  138
             doc.getDocumentElement().appendChild(element);
 212  138
             Entry entry = new Entry(element, bases, isDeleted);
 213  138
             element.setAttribute("name", name);
 214  138
             Entry oldEntry = entries.put(name, entry);
 215  138
             if (oldEntry != null) oldEntry.element.getParentNode().removeChild(oldEntry.element);
 216  138
             return entry;
 217  
         }
 218  
 
 219  
         protected State delete(String name, boolean doCopy) {
 220  50
             if (!entries.containsKey(name)) return this;
 221  41
             State newState = copy(doCopy);
 222  41
             Entry entry = newState.entries.remove(name);
 223  41
             entry.element.getParentNode().removeChild(entry.element);
 224  41
             return newState.checkEmpty(name);
 225  
         }
 226  
 
 227  
         protected State setDeleted(String name, boolean isDeleted, boolean doCopy) {
 228  20
             Entry entry = entries.get(name);
 229  20
             if (entry != null) {
 230  11
                 if (entry.isDeleted == isDeleted) return this;
 231  9
             } else if (!isDeleted) {
 232  0
                 return this;
 233  
             }
 234  20
             State newState = copy(doCopy);
 235  20
             entry = newState.newEntry(name, entry == null ? new LinkedHashMap<String, Element>() : newState.entries.get(name).bases, isDeleted);
 236  20
             if (isDeleted) {
 237  12
                 entry.element.setAttribute("deleted", "true");
 238  
             } else {
 239  8
                 entry.element.removeAttribute("deleted");
 240  
             }
 241  20
             return newState.checkEmpty(name);
 242  
         }
 243  
 
 244  
         protected State addBase(String name, String relative, boolean doCopy) {
 245  118
             Entry entry = entries.get(name);
 246  118
             if (entry != null && entry.bases.containsKey(relative)) return this;
 247  118
             State newState = copy(doCopy);
 248  118
             LinkedHashMap<String, Element> bases = entry != null ? new LinkedHashMap<String, Element>(entry.bases) : new LinkedHashMap<String, Element>();
 249  118
             Element base = newState.doc.createElement("base");
 250  118
             base.setAttribute("target", relative);
 251  118
             bases.put(relative, base);
 252  118
             newState.newEntry(name, bases, false).element.appendChild(base);
 253  118
             return newState;
 254  
         }
 255  
 
 256  
         protected State removeBase(String name, String relative, boolean doCopy) {
 257  6
             Entry entry = entries.get(name);
 258  6
             if (entry == null || !entry.bases.containsKey(relative)) return this;
 259  6
             State newState = copy(doCopy);
 260  6
             entry = newState.entries.get(name);
 261  6
             entry.element.removeChild(entry.bases.remove(relative));
 262  6
             return newState.checkEmpty(name);
 263  
         }
 264  
 
 265  
         protected void removeDeletedChildren(Collection<String> names) throws FileSystemException {
 266  62
             for (Entry entry: entries.values()) {
 267  69
                 if (entry.isDeleted) names.remove(entry.element.getAttribute("name"));
 268  
             }
 269  62
         }
 270  
 
 271  
         protected Collection<String> loadData(String name, boolean[] isDeleted) {
 272  682
             Entry entry = entries.get(name);
 273  682
             if (entry == null) return null;
 274  72
             isDeleted[0] = entry.isDeleted;
 275  72
             return entry.bases.keySet();
 276  
         }
 277  
 
 278  
         protected State submitRequests(String name, COWStorageHandler.Request... requests) {
 279  194
             State newState = copy(true);
 280  194
             int i = 0, j = 0;
 281  388
             for (COWStorageHandler.Request request: requests) {
 282  194
                 if (request instanceof COWStorageHandler.ClearRequest) {
 283  50
                     newState.delete(name, false);
 284  144
                 } else if (request instanceof COWStorageHandler.SetDeletedRequest) {
 285  20
                     newState.setDeleted(name, ((COWStorageHandler.SetDeletedRequest) request).deleted, false);
 286  124
                 } else if (request instanceof COWStorageHandler.AddBaseRequest) {
 287  118
                     newState.addBase(name, ((COWStorageHandler.AddBaseRequest) request).relative, false);
 288  6
                 } else if (request instanceof COWStorageHandler.RemoveBaseRequest) {
 289  6
                     newState.removeBase(name, ((COWStorageHandler.RemoveBaseRequest) request).relative, false);
 290  
                 }
 291  
             }
 292  194
             return newState.entries.isEmpty() ? null : newState;
 293  
         }
 294  
     }
 295  
 
 296  
     private final static class Entry {
 297  
         protected final Element element;
 298  
         protected final LinkedHashMap<String, Element> bases;
 299  
         protected final boolean isDeleted;
 300  
 
 301  335
         protected Entry(Element element, LinkedHashMap<String, Element> bases, boolean isDeleted) {
 302  335
             this.element = element;
 303  335
             this.bases = bases;
 304  335
             this.isDeleted = isDeleted;
 305  335
         }
 306  
     }
 307  
 }