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.comp;
015:
016: import java.beans.PropertyChangeListener;
017: import java.beans.PropertyChangeSupport;
018: import java.beans.PropertyVetoException;
019: import java.beans.VetoableChangeListener;
020: import java.beans.VetoableChangeSupport;
021: import java.util.ArrayList;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Map;
025: import java.util.Set;
026: import org.itsnat.comp.ItsNatComponentManager;
027: import org.itsnat.comp.ui.ItsNatComponentUI;
028: import org.itsnat.core.ItsNatDOMException;
029: import org.itsnat.core.ItsNatDocument;
030: import org.itsnat.core.ItsNatException;
031: import org.itsnat.core.event.ParamTransport;
032: import org.itsnat.core.NameValue;
033: import org.itsnat.impl.core.ItsNatDocumentImpl;
034: import org.itsnat.impl.core.ItsNatUserDataImpl;
035: import org.itsnat.impl.core.listener.MutationEventBeforeAfterListener;
036: import org.itsnat.impl.core.listener.dom.DOMEventTargetListenerList;
037: import org.itsnat.impl.core.listener.MutationEventListenerImpl;
038: import org.w3c.dom.Node;
039: import org.w3c.dom.events.Event;
040: import org.w3c.dom.events.EventListener;
041: import org.w3c.dom.events.EventTarget;
042: import org.w3c.dom.events.MutationEvent;
043:
044: /**
045: *
046: * @author jmarranz
047: */
048: public abstract class ItsNatComponentImpl extends ItsNatUserDataImpl
049: implements ItsNatComponentInternal, EventListener,
050: MutationEventBeforeAfterListener {
051: protected ItsNatComponentManagerImpl componentMgr;
052: protected Map artifacts;
053: protected DOMEventTargetListenerList userDOMListeners;
054: protected Set enabledDOMEvents = new HashSet();
055: protected Map evtListParams;
056: protected int syncMode;
057: protected boolean disposed = false;
058: protected boolean hasFocus; // Será siempre falso si el componente no puede tener foco (componentes "free")
059: protected PropertyChangeSupport changeSupport;
060: protected VetoableChangeSupport vetoableChangeSupport;
061: protected Object dataModel;
062: protected ItsNatComponentUI compUI; // puede ser null pues no todos tienen UI (ej. form)
063: protected Node node;
064: protected boolean mutationEventListener;
065: protected Object structure;
066:
067: /** Creates a new instance of ItsNatComponentImpl */
068: public ItsNatComponentImpl(Node node, NameValue[] artifacts,
069: ItsNatComponentManagerImpl componentMgr) {
070: super (false);
071:
072: this .componentMgr = componentMgr;
073:
074: this .syncMode = componentMgr.getItsNatDocument()
075: .getDefaultSyncMode();
076:
077: if (artifacts != null) {
078: Map artifactMap = getArtifactMap();
079: for (int i = 0; i < artifacts.length; i++) {
080: NameValue artif = artifacts[i];
081: artifactMap.put(artif.getName(), artif.getValue());
082: }
083: }
084:
085: if (node == null)
086: node = createDefaultNode(); // Puede devolver null
087: this .node = node; // Se tolera el caso de que sea nulo para permitir un posterior "attach" (ningún componente estándar por ahora)
088:
089: if (node != null) {
090: if (node.getParentNode() != null)
091: enableEventListeners(); // Si no estuviera en el árbol del documento dará error
092: else {
093: // Es un componente preparado para ser añadido y quitado del árbol continuamente, es usado por ejemplo por los componentes usados como editores
094: ItsNatDocumentImpl itsNatDoc = getItsNatDocumentImpl();
095: MutationEventListenerImpl mainListener = itsNatDoc
096: .getMutationEventListener();
097: mainListener.addMutationEventBeforeAfterListener(node,
098: this );
099: this .mutationEventListener = true;
100:
101: componentMgr.addExcludedNodeAsItsNatComponent(node); // Evita que un buildComponents lo añada, aunque el nodo no esté unido al árbol no importa desde el punto de vista del registro de ItsNatDocument
102: }
103: }
104: }
105:
106: public void init() {
107: // Debe llamarse desde alguna clase derivada como parte del proceso de creación
108:
109: setDefaultStructure();
110:
111: setDefaultItsNatComponentUI();
112: setDefaultModels();
113:
114: getItsNatComponentUI().setEnabled(true);
115: }
116:
117: public void setDefaultModels() {
118: // Derivar para los selection models
119: setDefaultDataModel();
120: }
121:
122: public void setDefaultStructure() {
123: if (structure == null) {
124: // Si no ha sido definida ya la obtenemos
125: Class structClass = getStructureClass();
126: if (structClass != null) // Si es null es que no tiene estructura
127: this .structure = getDeclaredStructure(structClass);
128: }
129: }
130:
131: protected void finalize() {
132: if (disposed)
133: return;
134:
135: // No se liberará nunca por el gc si tiene listeners "intrínsecos" registrados
136: // (el propio componente es el listener) y el documento sigue vivo,
137: // pues el registro sujeta el componente.
138: // Tampoco se liberará el listener vía gc del nodo, pues el componente sujeta
139: // con una referencia normal el nodo.
140: // Ha de haberse hecho un dispose() previo para desregistrar.
141:
142: /*
143: ItsNatDocument itsNatDoc = getItsNatDocument(); // No hace falta sincronizar
144: if (itsNatDoc.isInvalid()) // No hace falta sincronizar la llamada y además si es inválido nunca será de nuevo valido, no hay problema de cambio
145: return;
146:
147: synchronized(itsNatDoc)
148: {
149: // No tengo claro que sirva para algo esto por lo de más arriba
150: boolean updateClient = !itsNatDoc.isInvalid(); // por si se ha hecho inválido antes de entrar en el synchronized
151: dispose(updateClient);
152: // Si updateClient = true liberamos en el cliente
153: // el listener que puede estar asociado a algún nodo
154: // aunque ya no esté asociado al documento está sujeto
155: // por el array de los listeners. Así evitamos
156: // memory leaks en el cliente.
157: }
158: */
159: }
160:
161: public void dispose() {
162: dispose(true);
163: }
164:
165: public void dispose(boolean updateClient) {
166: if (disposed)
167: return;
168:
169: unbindModels();
170:
171: disableEventListeners(updateClient);
172:
173: componentMgr.removeItsNatComponent(this , false);
174: if (mutationEventListener) {
175: ItsNatDocumentImpl itsNatDoc = getItsNatDocumentImpl();
176: MutationEventListenerImpl mainListener = itsNatDoc
177: .getMutationEventListener();
178: Node node = getNode();
179: mainListener.removeMutationEventBeforeAfterListener(node,
180: this );
181: componentMgr.removeExcludedNodeAsItsNatComponent(node);
182: }
183: this .disposed = true;
184: }
185:
186: public Node getNode() {
187: return node;
188: }
189:
190: public void setNode(Node node) {
191: // Derivar si se permite "reattachment"
192: throw new ItsNatDOMException(
193: "This component cannot be reattached", node);
194: }
195:
196: public abstract ItsNatComponentUI createDefaultItsNatComponentUI();
197:
198: public void setDefaultItsNatComponentUI() {
199: setItsNatComponentUI(createDefaultItsNatComponentUI());
200: }
201:
202: public abstract Node createDefaultNode();
203:
204: public boolean hasFocus() {
205: return hasFocus;
206: }
207:
208: public void setHasFocus(boolean hasFocus) {
209: this .hasFocus = hasFocus;
210: }
211:
212: public ItsNatComponentManager getItsNatComponentManager() {
213: return componentMgr;
214: }
215:
216: public ItsNatComponentManagerImpl getItsNatComponentManagerImpl() {
217: return componentMgr;
218: }
219:
220: public ItsNatDocumentImpl getItsNatDocumentImpl() {
221: return getItsNatComponentManagerImpl().getItsNatDocumentImpl();
222: }
223:
224: public ItsNatDocument getItsNatDocument() {
225: return getItsNatDocumentImpl();
226: }
227:
228: public ItsNatComponentUI getItsNatComponentUI() {
229: return compUI;
230: }
231:
232: public void setItsNatComponentUI(ItsNatComponentUI compUI) {
233: this .compUI = compUI;
234: }
235:
236: public boolean hasArtifacts() {
237: if (artifacts == null)
238: return false;
239: return !artifacts.isEmpty();
240: }
241:
242: public Map getArtifactMap() {
243: if (artifacts == null)
244: this .artifacts = new HashMap();
245: return artifacts;
246: }
247:
248: public void registerArtifact(String name, Object value) {
249: Map artifacts = getArtifactMap();
250: artifacts.put(name, value);
251: }
252:
253: public Object getArtifact(String name) {
254: if (!hasArtifacts())
255: return null;
256:
257: Map artifacts = getArtifactMap();
258: return artifacts.get(name);
259: }
260:
261: public Object removeArtifact(String name) {
262: Map artifacts = getArtifactMap();
263: return artifacts.remove(name);
264: }
265:
266: public Object getArtifact(String name, boolean cascade) {
267: Object artif = getArtifact(name);
268: if (cascade && (artif == null))
269: artif = getItsNatDocumentImpl().getArtifact(name, true);
270: return artif;
271: }
272:
273: public DOMEventTargetListenerList getUserDOMListeners() {
274: if (userDOMListeners == null) // Para ahorrar memoria si no se usa (ej. Labels)
275: userDOMListeners = new DOMEventTargetListenerList(true);
276: return userDOMListeners;
277: }
278:
279: public void addEventListener(String type, EventListener listener) {
280: enableEventListener(type); // primero activamos porque sino no llegan
281: getUserDOMListeners().addEventListener(type, false, listener);
282: }
283:
284: public void removeEventListener(String type, EventListener listener) {
285: getUserDOMListeners()
286: .removeEventListener(type, false, listener);
287: // No hacemos disableEventListener porque son cosas diferentes
288: }
289:
290: public boolean isEnabledEventListener(String type) {
291: return enabledDOMEvents.contains(type);
292: }
293:
294: protected abstract ParamTransport[] getParamTransports(String type);
295:
296: public Map getEventListenerParamMap() {
297: if (evtListParams == null)
298: evtListParams = new HashMap(); // lazy load
299: return evtListParams;
300: }
301:
302: public void setEventListenerParams(String type, boolean useCapture,
303: int syncMode, ParamTransport[] extraParams,
304: String preSendCode, long ajaxTimeout) {
305: EventListenerParams params = new EventListenerParams(
306: useCapture, syncMode, extraParams, preSendCode,
307: ajaxTimeout);
308: Map evtListParams = getEventListenerParamMap();
309: evtListParams.put(type, params); // Substituye el que ya hubiera (si existiera)
310:
311: reloadEventListener(type); // Se quita y se carga de nuevo el listener de este tipo (si hubiera)
312: }
313:
314: public EventListenerParams getEventListenerParams(String type) {
315: if (evtListParams == null)
316: return null;
317: return (EventListenerParams) evtListParams.get(type); // puede ser null
318: }
319:
320: public void addInternalEventListener(String type) {
321: ItsNatDocumentImpl itsNatDoc = getItsNatDocumentImpl();
322:
323: boolean useCapture;
324: int syncMode;
325: ParamTransport[] extraParamsUser;
326: String preSendCode;
327: long ajaxTimeout;
328:
329: EventListenerParams params = getEventListenerParams(type);
330: if (params != null) {
331: useCapture = params.isUseCapture();
332: syncMode = params.getSyncMode();
333: extraParamsUser = params.getExtraParams();
334: preSendCode = params.getPreSendCode();
335: ajaxTimeout = params.getAjaxTimeout();
336: } else {
337: useCapture = false;
338: syncMode = itsNatDoc.getDefaultSyncMode();
339: extraParamsUser = null;
340: preSendCode = null;
341: ajaxTimeout = itsNatDoc.getAJAXTimeout();
342: }
343:
344: ParamTransport[] extraParamsInt = getParamTransports(type);
345:
346: ParamTransport[] extraParamsFinal = null;
347: if ((extraParamsUser != null) || (extraParamsInt != null)) {
348: if (extraParamsUser == null)
349: extraParamsFinal = extraParamsInt; // Si es null pues vale también
350: else if (extraParamsInt == null)
351: extraParamsFinal = extraParamsUser; // "
352: else {
353: // Los dos arrays existen
354: ArrayList auxArray = new ArrayList();
355: for (int i = 0; i < extraParamsUser.length; i++)
356: auxArray.add(extraParamsUser[i]);
357: for (int i = 0; i < extraParamsInt.length; i++)
358: auxArray.add(extraParamsInt[i]);
359:
360: extraParamsFinal = (ParamTransport[]) auxArray
361: .toArray(new ParamTransport[auxArray.size()]);
362: }
363: }
364:
365: itsNatDoc.addEventListener((EventTarget) getNode(), type, this ,
366: useCapture, syncMode, extraParamsFinal, preSendCode,
367: ajaxTimeout);
368: }
369:
370: public void removeInternalEventListener(String type,
371: boolean updateClient) {
372: ItsNatDocumentImpl itsNatDoc = getItsNatDocumentImpl();
373: itsNatDoc.removeDOMEventListener((EventTarget) getNode(), type,
374: this , false, updateClient);
375: }
376:
377: public void enableEventListener(String type) {
378: if (isEnabledEventListener(type))
379: return; // ya fue activado
380: addInternalEventListener(type);
381: enabledDOMEvents.add(type);
382: }
383:
384: public void disableEventListener(String type) {
385: disableEventListener(type, true);
386: }
387:
388: public void disableEventListener(String type, boolean updateClient) {
389: removeInternalEventListener(type, updateClient);
390: enabledDOMEvents.remove(type);
391: }
392:
393: public void disableEventListeners(boolean updateClient) {
394: Object[] types = enabledDOMEvents.toArray();
395: for (int i = 0; i < types.length; i++) {
396: String type = (String) types[i];
397: disableEventListener(type, updateClient);
398: }
399: }
400:
401: public void reloadEventListener(String type) {
402: // Es usado ante un cambio de parámetros, lo quitamos y añadimos
403: // de nuevo.
404: // Los del usuario no se tocan ni dependen de los parámetros
405: // por lo que no es necesario que se quiten y se añadan de nuevo
406: if (!enabledDOMEvents.contains(type))
407: return;
408:
409: disableEventListener(type);
410: enableEventListener(type);
411: }
412:
413: public void addPropertyChangeListener(
414: PropertyChangeListener listener) {
415: if (listener == null)
416: return;
417:
418: if (changeSupport == null)
419: changeSupport = new PropertyChangeSupport(this );
420:
421: changeSupport.addPropertyChangeListener(listener);
422: }
423:
424: public void removePropertyChangeListener(
425: PropertyChangeListener listener) {
426: if (listener == null || changeSupport == null)
427: return;
428:
429: changeSupport.removePropertyChangeListener(listener);
430: }
431:
432: public PropertyChangeListener[] getPropertyChangeListeners() {
433: if (changeSupport == null)
434: return new PropertyChangeListener[0];
435:
436: return changeSupport.getPropertyChangeListeners();
437: }
438:
439: public void addPropertyChangeListener(String propertyName,
440: PropertyChangeListener listener) {
441: if (listener == null)
442: return;
443:
444: if (changeSupport == null)
445: changeSupport = new PropertyChangeSupport(this );
446:
447: changeSupport.addPropertyChangeListener(propertyName, listener);
448: }
449:
450: public void removePropertyChangeListener(String propertyName,
451: PropertyChangeListener listener) {
452: if (listener == null || changeSupport == null)
453: return;
454:
455: changeSupport.removePropertyChangeListener(propertyName,
456: listener);
457: }
458:
459: public PropertyChangeListener[] getPropertyChangeListeners(
460: String propertyName) {
461: if (changeSupport == null)
462: return new PropertyChangeListener[0];
463:
464: return changeSupport.getPropertyChangeListeners(propertyName);
465: }
466:
467: protected void firePropertyChange(String propertyName,
468: Object oldValue, Object newValue) {
469: PropertyChangeSupport changeSupport = this .changeSupport;
470: if ((changeSupport == null)
471: || ((oldValue != null) && (newValue != null) && oldValue
472: .equals(newValue)))
473: return;
474:
475: changeSupport.firePropertyChange(propertyName, oldValue,
476: newValue);
477: }
478:
479: public void addVetoableChangeListener(
480: VetoableChangeListener listener) {
481: if (listener == null)
482: return;
483:
484: if (vetoableChangeSupport == null)
485: vetoableChangeSupport = new VetoableChangeSupport(this );
486:
487: vetoableChangeSupport.addVetoableChangeListener(listener);
488: }
489:
490: public void removeVetoableChangeListener(
491: VetoableChangeListener listener) {
492: if (listener == null || changeSupport == null)
493: return;
494:
495: vetoableChangeSupport.removeVetoableChangeListener(listener);
496: }
497:
498: public VetoableChangeListener[] getVetoableChangeListeners() {
499: if (vetoableChangeSupport == null)
500: return new VetoableChangeListener[0];
501:
502: return vetoableChangeSupport.getVetoableChangeListeners();
503: }
504:
505: protected void fireVetoableChange(String propertyName,
506: Object oldValue, Object newValue)
507: throws PropertyVetoException {
508: if (vetoableChangeSupport == null)
509: return;
510: // Incluso aunque sea igual el valor hacemos fire así podemos detectar un intento de cambio (aunque sea al mismo valor) de una propiedad de sólo lectura
511: // de igual manera que se hace en JComponent
512: vetoableChangeSupport.fireVetoableChange(propertyName,
513: oldValue, newValue);
514: }
515:
516: public void setDefaultDataModel() {
517: Object dataModel = createDefaultModelInternal();
518: if (dataModel != null)
519: setDataModel(dataModel);
520: // Si es null es que no tiene modelo
521: }
522:
523: public void unbindModels() {
524: unbindDataModel();
525: }
526:
527: public abstract void bindDataModel();
528:
529: public abstract void unbindDataModel();
530:
531: public Object getDataModel() {
532: return dataModel;
533: }
534:
535: public void setDataModel(Object dataModel) {
536: setDataModel(dataModel, false);
537: }
538:
539: public void setDataModel(Object dataModel, boolean acceptNull) {
540: if ((dataModel == null) && !acceptNull)
541: throw new ItsNatException("Data model cannot be null");
542:
543: // Aunque el nuevo modelo sea el mismo que el que ya hay
544: // hacemos unbindDataModel() siempre pues de esta manera podemos hacer
545: // una especie de reset, éste es útil para poder añadir un listener desde fuera
546: // que se llame antes del listener interno en el caso del data model
547: // por defecto, si se hace "reset" el listener se quitará y se añadirá de nuevo
548: // quedando el último.
549:
550: if (this .dataModel != null)
551: unbindDataModel();
552:
553: this .dataModel = dataModel;
554:
555: // El dataModel del usuario manda sobre el DOM
556: syncWithDataModel();
557:
558: // A partir de ahora los cambios los repercutimos en el DOM por eventos
559: // No se debe cambiar el DOM por otra vía que por el objeto dataModel
560: bindDataModel();
561: }
562:
563: public void handleEvent(Event evt) {
564: if (!getItsNatComponentUI().isEnabled())
565: return;
566:
567: processDOMEvent(evt);
568: }
569:
570: public void processDOMEvent(Event evt) {
571: String type = evt.getType();
572: if (type.equals("focus"))
573: setHasFocus(true);
574: else if (type.equals("blur"))
575: setHasFocus(false);
576:
577: // Derivar para hacer lo específico antes de delegar en los listeners del usuario
578: getUserDOMListeners().dispatchEvent(evt, false);
579: }
580:
581: public void syncWithDataModel() {
582: syncUIWithDataModel();
583: }
584:
585: public void before(MutationEvent evt) {
586: String type = evt.getType();
587: if (type.equals("DOMNodeRemoved")) {
588: // Quitamos el listener porque si se añade de nuevo el nodo
589: // al documento, en el cliente será un objeto nuevo por lo que
590: // habrá que registrar de nuevo el listener
591: disableEventListeners(true);
592: }
593: }
594:
595: public void after(MutationEvent evt) {
596: String type = evt.getType();
597: if (type.equals("DOMNodeInserted")) {
598: enableEventListeners();
599: }
600: }
601:
602: public abstract void enableEventListeners();
603:
604: public abstract Class getStructureClass();
605:
606: public Object getStructure() {
607: return structure;
608: }
609:
610: public abstract Object getDeclaredStructure(Class expectedClass);
611:
612: }
|