001: /*
002: ItsNat Java Web Application Framework
003: Copyright (C) 2007 Innowhere Software Services S.L., Spanish Company
004: Author: Jose Maria Arranz Santamaria
005:
006: This program is free software: you can redistribute it and/or modify
007: it under the terms of the GNU Affero General Public License as published by
008: the Free Software Foundation, either version 3 of the License, or
009: (at your option) any later version. See the GNU Affero General Public
010: License for more details. See the copy of the GNU Affero General Public License
011: included in this program. If not, see <http://www.gnu.org/licenses/>.
012: */
013:
014: package org.itsnat.impl.core;
015:
016: import java.util.ArrayList;
017: import java.util.HashMap;
018: import java.util.Iterator;
019: import java.util.LinkedList;
020: import java.util.Map;
021: import org.itsnat.core.ItsNatException;
022: import org.w3c.dom.Node;
023:
024: /**
025: * Recuerda los nodos cacheados usando un id único que no puede ser reutilizado
026: * aunque el nodo se quite.
027: * Los nodos cacheados deben pertenecer al árbol DOM, es decir están dentro del Document. *
028: *
029: * No usamos la colección con WeakReferences porque cuando el nodo (subárbol) se quita del árbol
030: * es detectado automáticamente con un mutation listener que elimina dicho nodo
031: * de la caché (si estuviera) así como todos los hijos del subárbol y por tanto
032: * deja de referenciarse y puede recogerse el nodo por el garbage collector.
033: *
034: * @author jmarranz
035: */
036: public class NodeCache {
037: protected ItsNatDocumentImpl itsNatDoc; // No se usa pero para que quede claro que es "por documento"
038: protected Map nodeMap = new HashMap();
039: protected Map idMap = new HashMap();
040:
041: /** Creates a new instance of NodeCache */
042: public NodeCache(ItsNatDocumentImpl itsNatDoc) {
043: this .itsNatDoc = itsNatDoc;
044: }
045:
046: public Iterator iterator() {
047: return nodeMap.entrySet().iterator();
048: }
049:
050: public boolean isCacheableNode(Node node) {
051: // Cacheamos cualquier nodo excepto el propio Document, la vista, DocumentType
052: // pues es absurdo cachearlos pues son "singleton",
053: // y nodos de texto, pues los nodos de texto no admiten asociar una propiedad (el id)
054: // en el MSIE y además fácilmente son filtrados por los browsers etc
055:
056: int type = node.getNodeType();
057: if (type == Node.ELEMENT_NODE)
058: return true; // El caso típico, para acelerar
059: else if (type == Node.TEXT_NODE)
060: return false; // Otro caso típico, para acelerar
061: else if (type == Node.DOCUMENT_NODE)
062: return false;
063: else if (type == AbstractViewImpl.ABSTRACT_VIEW)
064: return false;
065: else if (type == Node.DOCUMENT_TYPE_NODE)
066: return false;
067:
068: return true; // Comentarios etc
069: }
070:
071: public String removeNode(Node node) {
072: if (!isCacheableNode(node))
073: return null; // Nos ahorramos tiempo en buscar
074:
075: String id = (String) nodeMap.remove(node);
076: if (id == null)
077: return null;
078:
079: idMap.remove(id);
080:
081: return id;
082: }
083:
084: public String getId(Node node) {
085: if (node == null)
086: return null;
087:
088: if (!isCacheableNode(node))
089: return null; // Nos ahorramos tiempo de búsqueda
090:
091: return (String) nodeMap.get(node);
092: }
093:
094: public Node getNodeById(String id) {
095: return (Node) idMap.get(id);
096: }
097:
098: public String addNode(Node node) {
099: // El id debe ser único y vinculado al nodo unívoca e inequívocamente
100: // (no debería existir el mismo id para otro nodo) y no debería
101: // reutilizarse un id que ya fue usado por otro nodo aunque ya no exista
102:
103: if (node == null)
104: throw new ItsNatException("Null node is not supported");
105:
106: if (!isCacheableNode(node))
107: return null; // No se puede cachear
108:
109: /* Evitamos cachear cuando alguno de los clientes (propietario u observadores)
110: * no pueda recibir código de respuesta, porque el objetivo de este nuevo id es que
111: * viaje a todos los clientes, propietario y observadores, para añadirse
112: * en la cache del cliente, si uno de los clientes no añade el nodo a su caché
113: * entonces cuando reciba el id no encontrará el nodo.
114: * Ya existirá otra oportunidad de cachear el nodo, la cache no es imprescindible simplemente acelera.
115: */
116: if (!itsNatDoc.allClientDocumentWillReceiveCodeSended())
117: return null;
118:
119: String id = itsNatDoc.generateUniqueId();
120:
121: nodeMap.put(node, id);
122: idMap.put(id, node);
123:
124: return id;
125: }
126:
127: public boolean isEmpty() {
128: boolean res = idMap.isEmpty();
129: if (res != nodeMap.isEmpty())
130: throw new ItsNatException("INTERNAL ERROR");
131: return res;
132: }
133:
134: public ArrayList getOrderedByHeight() {
135: /* Este método es usado por el control remoto, se debe a que
136: * los nodos no están ordenados de ninguna forma en la caché
137: * y al replicar la caché en el browser remoto necesitamos
138: * enviar todos los nodos con su id y calculando su path, usando paths absolutos
139: * no hay problema pero es un proceso muy lento, si utilizamos paths
140: * relativos no sabemos si el padre que hemos encontrado en la caché
141: * lo hemos enviado antes al browser y está ya cacheado allí.
142: * Por ello una técnica es ordenar los nodos por alturas tal que
143: * si se envían primero los más altos los más bajos encontrarán ya
144: * en el browser el padre cacheado (si existe) pues este es más alto.
145: * Entre nodos de la misma altura no hay problema de orden pues ninguno
146: * es padre del otro, no hay dependencias a la hora de calcular el path.
147: * El ArrayList devuelto procesar pero no memorizar pues contiene los Map.Entry
148: * de este caché.
149: * Puede haber alturas en donde no haya ningún nodo (lo normal).
150: */
151:
152: ArrayList cacheCopy = new ArrayList();
153: for (Iterator it = iterator(); it.hasNext();) {
154: Map.Entry entry = (Map.Entry) it.next();
155: Node node = (Node) entry.getKey();
156: int h = getNodeDeep(node);
157: // Aseguramos que cacheCopy contiene la posición h
158: if (cacheCopy.size() <= h) {
159: int currSize = cacheCopy.size();
160: for (int i = 1; i <= h - currSize + 1; i++)
161: cacheCopy.add(null);
162: }
163: LinkedList sameH = (LinkedList) cacheCopy.get(h);
164: if (sameH == null) {
165: sameH = new LinkedList();
166: cacheCopy.set(h, sameH);
167: }
168: sameH.add(entry);
169: }
170: return cacheCopy;
171: }
172:
173: private static int getNodeDeep(Node node) {
174: int i = 0;
175: while (node != null) {
176: i++;
177: node = node.getParentNode();
178: }
179: return i;
180: }
181: }
|