View Javadoc
1   /**
2    * 
3    * Copyright (c) 2014, Openflexo
4    * 
5    * This file is part of Diana-core, a component of the software infrastructure 
6    * developed at Openflexo.
7    * 
8    * 
9    * Openflexo is dual-licensed under the European Union Public License (EUPL, either 
10   * version 1.1 of the License, or any later version ), which is available at 
11   * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
12   * and the GNU General Public License (GPL, either version 3 of the License, or any 
13   * later version), which is available at http://www.gnu.org/licenses/gpl.html .
14   * 
15   * You can redistribute it and/or modify under the terms of either of these licenses
16   * 
17   * If you choose to redistribute it and/or modify under the terms of the GNU GPL, you
18   * must include the following additional permission.
19   *
20   *          Additional permission under GNU GPL version 3 section 7
21   *
22   *          If you modify this Program, or any covered work, by linking or 
23   *          combining it with software containing parts covered by the terms 
24   *          of EPL 1.0, the licensors of this Program grant you additional permission
25   *          to convey the resulting work. * 
26   * 
27   * This software is distributed in the hope that it will be useful, but WITHOUT ANY 
28   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
29   * PARTICULAR PURPOSE. 
30   *
31   * See http://www.openflexo.org/license.html for details.
32   * 
33   * 
34   * Please contact Openflexo (openflexo-contacts@openflexo.org)
35   * or visit www.openflexo.org if you need additional information.
36   * 
37   */
38  
39  package org.openflexo.gina.swing.editor.inspector;
40  
41  import java.beans.PropertyChangeEvent;
42  import java.beans.PropertyChangeListener;
43  import java.beans.PropertyChangeSupport;
44  import java.lang.reflect.Type;
45  import java.util.ArrayList;
46  import java.util.HashMap;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.logging.Level;
50  import java.util.logging.Logger;
51  
52  import org.openflexo.connie.type.TypeUtils;
53  import org.openflexo.gina.model.FIBModelFactory;
54  import org.openflexo.gina.model.FIBModelObject;
55  import org.openflexo.gina.model.FIBProperty;
56  import org.openflexo.gina.swing.editor.controller.FIBEditorController;
57  import org.openflexo.logging.FlexoLogger;
58  import org.openflexo.pamela.CloneableProxyObject;
59  import org.openflexo.pamela.KeyValueCoding;
60  import org.openflexo.pamela.undo.CompoundEdit;
61  import org.openflexo.toolbox.HasPropertyChangeSupport;
62  
63  /**
64   * Abstraction of a container synchronized with and reflecting a selection<br>
65   * <ul>
66   * <li>If selection is empty, then manage a default value (this value will be used to build new objects)</li>
67   * <li>If selection is unique, manage value of unique selection</li>
68   * <li>If selection is multiple, manage value of entire selection, by batch</li>
69   * </ul>
70   * 
71   * @author sylvain
72   * 
73   * @param <S>
74   */
75  public abstract class InspectedProperties<S extends KeyValueCoding> implements HasPropertyChangeSupport, PropertyChangeListener {
76  
77  	private static final Logger logger = FlexoLogger.getLogger(InspectedProperties.class.getPackage().getName());
78  
79  	private final FIBEditorController controller;
80  	private S defaultValue;
81  
82  	private final PropertyChangeSupport pcSupport;
83  
84  	private boolean isDeleted = false;
85  
86  	private boolean shouldBeUpdated = true;
87  
88  	protected InspectedProperties(FIBEditorController controller, S defaultValue) {
89  		this.controller = controller;
90  		this.defaultValue = defaultValue;
91  		pcSupport = new PropertyChangeSupport(this);
92  	}
93  
94  	public FIBEditorController getController() {
95  		return controller;
96  	}
97  
98  	protected Map<FIBProperty<?>, Object> storedPropertyValues = new HashMap<>();
99  
100 	/**
101 	 * Return property value matching supplied parameter for current selection<br>
102 	 * <ul>
103 	 * <li>If selection is empty, then return default value (this value will be used to build new objects)</li>
104 	 * <li>If selection is unique, then return the right value</li>
105 	 * <li>If selection is multiple, return value of first selected object</li>
106 	 * </ul>
107 	 * Store the result for future comparison evaluations
108 	 * 
109 	 * @param property
110 	 * @return
111 	 */
112 	public <T> T getPropertyValue(FIBProperty<T> property) {
113 		T returned = _getPropertyValue(property);
114 		if (logger.isLoggable(Level.FINE)) {
115 			logger.fine("Requesting " + property + " for " + getSelection() + ", returning " + returned);
116 		}
117 		storedPropertyValues.put(property, returned);
118 		return returned;
119 	}
120 
121 	/**
122 	 * Return property value matching supplied parameter for current selection<br>
123 	 * Do not store the result for future comparison evaluations
124 	 * 
125 	 * @param property
126 	 * @return
127 	 */
128 	protected <T> T _getPropertyValue(FIBProperty<T> property) {
129 		T returned;
130 		if (getSelection().size() == 0) {
131 			if (defaultValue != null && defaultValue.hasKey(property.getName())) {
132 				returned = (T) defaultValue.objectForKey(property.getName());
133 			}
134 			else {
135 				returned = null;
136 			}
137 		}
138 		else {
139 			S style = getStyle(getSelection().get(0));
140 			if (style != null && style.hasKey(property.getName())) {
141 				returned = (T) style.objectForKey(property.getName());
142 			}
143 			else {
144 				/*if (style != null) {
145 					System.out.println("OK, j'ai bien un " + style.getClass().getSimpleName() + " mais c'est dur de lui appliquer "
146 							+ parameter);
147 					System.out.println("parameter.getDeclaringClass()=" + parameter.getDeclaringClass());
148 					System.out.println("style.getClass()=" + style.getClass());
149 				}*/
150 				returned = null;
151 			}
152 		}
153 		if (property.getType() != null && property.getType().isPrimitive() && returned == null) {
154 			return property.getDefaultValue();
155 		}
156 		return returned;
157 	}
158 
159 	/**
160 	 * Sets property value matching supplied parameter for current selection, with supplied value<br>
161 	 * <ul>
162 	 * <li>If selection is empty, then sets default value (this value will be used to build new objects)</li>
163 	 * <li>If selection is unique, then sets the right value</li>
164 	 * <li>If selection is multiple, sets value of entire selection</li>
165 	 * </ul>
166 	 * Store the result for future comparison evaluations
167 	 * 
168 	 * @param parameter
169 	 * @param value
170 	 */
171 	public <T> void setPropertyValue(FIBProperty<T> parameter, T value) {
172 		T oldValue = getPropertyValue(parameter);
173 		// System.out.println("Sets from " + oldValue + " to " + value);
174 		if (requireChange(oldValue, value)) {
175 			if (getSelection().size() == 0) {
176 				if (defaultValue == null) {
177 					logger.warning("Cannot set " + parameter + " to " + value + " : no default value defined for " + this);
178 					return;
179 				}
180 				defaultValue.setObjectForKey(value, parameter.getName());
181 			}
182 			else {
183 				CompoundEdit setValueEdit = startRecordEdit("Set " + parameter.getName() + " to " + value);
184 				for (FIBModelObject n : getSelection()) {
185 					S style = getStyle(n);
186 					// System.out.println("For " + n + " use " + style + " and sets value of " + parameter.getName() + " with " + value);
187 					if (style != null) {
188 						style.setObjectForKey(value, parameter.getName());
189 					}
190 				}
191 				stopRecordEdit(setValueEdit);
192 			}
193 			storedPropertyValues.put(parameter, value);
194 			pcSupport.firePropertyChange(parameter.getName(), oldValue, value);
195 		}
196 	}
197 
198 	protected CompoundEdit startRecordEdit(String editName) {
199 		if (getController().getUndoManager() != null && !getController().getUndoManager().isUndoInProgress()
200 				&& !getController().getUndoManager().isRedoInProgress()) {
201 			return getController().getUndoManager().startRecording(editName);
202 		}
203 		return null;
204 	}
205 
206 	protected void stopRecordEdit(CompoundEdit edit) {
207 		if (edit != null && getController().getUndoManager() != null) {
208 			getController().getUndoManager().stopRecording(edit);
209 		}
210 	}
211 
212 	/**
213 	 * Equals method allowing null values
214 	 * 
215 	 * @param oldObject
216 	 * @param newObject
217 	 * @return
218 	 */
219 	protected boolean requireChange(Object oldObject, Object newObject) {
220 		if (oldObject == null) {
221 			if (newObject == null) {
222 				return false;
223 			}
224 			else {
225 				return true;
226 			}
227 		}
228 		return !oldObject.equals(newObject);
229 	}
230 
231 	/**
232 	 * Abstract method returning sub-selection on which inspected style may apply (return all objects relevant for such style)
233 	 * 
234 	 * @return
235 	 */
236 	public abstract List<? extends FIBModelObject> getSelection();
237 
238 	/**
239 	 * Return relevant style for a given {@link FIBModelObject}
240 	 * 
241 	 * @param node
242 	 * @return
243 	 */
244 	public abstract S getStyle(FIBModelObject object);
245 
246 	/**
247 	 * Return value identified as default values (values that are used when selection is empty)
248 	 * 
249 	 * @return
250 	 */
251 	public S getDefaultValue() {
252 		return defaultValue;
253 	}
254 
255 	/**
256 	 * Sets value identified as default values (values that are used when selection is empty)
257 	 * 
258 	 * @param defaultValue
259 	 */
260 	public void setDefaultValue(S defaultValue) {
261 		this.defaultValue = defaultValue;
262 	}
263 
264 	public FIBModelFactory getFactory() {
265 		// Not relevant
266 		return null;
267 	}
268 
269 	/**
270 	 * Generate new style using supplied factory and inspected property values
271 	 * 
272 	 * @param factory
273 	 * @return
274 	 */
275 	public S cloneStyle() {
276 		if (defaultValue instanceof CloneableProxyObject) {
277 			if (getFactory() != null && getFactory().getEditingContext() != null
278 					&& getFactory().getEditingContext().getUndoManager() != null) {
279 				getFactory().getEditingContext().getUndoManager().enableAnticipatedRecording();
280 			}
281 			S returned = (S) ((CloneableProxyObject) defaultValue).cloneObject();
282 			if (getFactory() != null && getFactory().getEditingContext() != null
283 					&& getFactory().getEditingContext().getUndoManager() != null) {
284 				getFactory().getEditingContext().getUndoManager().disableAnticipatedRecording();
285 			}
286 			return returned;
287 		}
288 		logger.warning("Could not clone " + defaultValue);
289 		return defaultValue;
290 	}
291 
292 	private final List<S> inspectedStyles = new ArrayList<>();
293 
294 	/**
295 	 * Called to "tell" inspected style that the selection has changed and then resulting inspected style might be updated<br>
296 	 * 
297 	 */
298 	public void fireSelectionUpdated() {
299 
300 		update();
301 	}
302 
303 	/**
304 	 * Called to update inspected style<br>
305 	 * 
306 	 */
307 	public void update() {
308 
309 		// We first unregister all existing observing scheme
310 		for (S s : inspectedStyles) {
311 			if (s instanceof HasPropertyChangeSupport && ((HasPropertyChangeSupport) s).getPropertyChangeSupport() != null) {
312 				((HasPropertyChangeSupport) s).getPropertyChangeSupport().removePropertyChangeListener(this);
313 			} /* else if (s instanceof Observable) {
314 				((Observable) s).deleteObserver(this);
315 				}*/
316 		}
317 		inspectedStyles.clear();
318 
319 		// Then, we observe all styles being selected
320 		for (FIBModelObject n : getSelection()) {
321 			S s = getStyle(n);
322 			if (s instanceof HasPropertyChangeSupport) {
323 				inspectedStyles.add(s);
324 				// System.out.println("!!!!!!!!!!!!!!! Observing " + s + " for " + n);
325 				if (((HasPropertyChangeSupport) s).getPropertyChangeSupport() != null) {
326 					((HasPropertyChangeSupport) s).getPropertyChangeSupport().addPropertyChangeListener(this);
327 				}
328 			} /* else if (s instanceof Observable) {
329 				inspectedStyles.add(s);
330 				((Observable) s).addObserver(this);
331 				}*/
332 		}
333 
334 		// Then we look if some properties have changed due to new selection
335 		fireChangedProperties();
336 	}
337 
338 	/**
339 	 * Internally called to fire change events between previously registered values and current resulting values
340 	 */
341 	protected void fireChangedProperties() {
342 
343 		if (getInspectedStyleClass() != null) {
344 			for (FIBProperty<?> p : FIBProperty.getFIBProperties(getInspectedStyleClass())) {
345 				fireChangedProperty(p);
346 			}
347 		}
348 	}
349 
350 	protected Class<? extends S> getInspectedStyleClass() {
351 		return (Class<? extends S>) TypeUtils.getBaseClass(TypeUtils.getTypeArgument(getClass(), InspectedProperties.class, 0));
352 	}
353 
354 	/**
355 	 * Internally called to fire change events between previously registered values and current resulting values<br>
356 	 * Do this only when needed on supplied FIBProperty
357 	 */
358 	protected <T> void fireChangedProperty(FIBProperty<T> p) {
359 		@SuppressWarnings("unchecked")
360 		T storedValue = (T) storedPropertyValues.get(p);
361 		T newValue = _getPropertyValue(p);
362 		if (requireChange(storedValue, newValue)) {
363 			_doFireChangedProperty(p, storedValue, newValue);
364 		}
365 	}
366 
367 	protected <T> void _doFireChangedProperty(FIBProperty<T> p, T oldValue, T newValue) {
368 		pcSupport.firePropertyChange(p.getName(), oldValue, newValue);
369 		// System.out.println("fired changed property " + p + " from " + oldValue + " to " + newValue);
370 		// setChanged();
371 	}
372 
373 	protected <T> void forceFireChangedProperty(FIBProperty<T> p) {
374 		@SuppressWarnings("unchecked")
375 		T storedValue = (T) storedPropertyValues.get(p);
376 		T newValue = _getPropertyValue(p);
377 		if (requireChange(storedValue, newValue)) {
378 			_doFireChangedProperty(p, storedValue, newValue);
379 		}
380 		else { // otherwise, we force it
381 			pcSupport.firePropertyChange(p.getName(), null, newValue);
382 			// setChanged();
383 		}
384 
385 	}
386 
387 	/**
388 	 * Called when a style composing current selection has changed a property.<br>
389 	 * We just call #fireChangedProperties()
390 	 */
391 	@Override
392 	public void propertyChange(PropertyChangeEvent evt) {
393 		// System.out.println("****************** PropertyChange with " + evt + " property=" + evt.getPropertyName());
394 		if (shouldBeUpdated) {
395 			fireChangedProperties();
396 		}
397 	}
398 
399 	public boolean shouldBeUpdated() {
400 		return shouldBeUpdated;
401 	}
402 
403 	public void setShouldBeUpdated(boolean shouldBeUpdated) {
404 		this.shouldBeUpdated = shouldBeUpdated;
405 	}
406 
407 	@Override
408 	public PropertyChangeSupport getPropertyChangeSupport() {
409 		return pcSupport;
410 	}
411 
412 	public void destroy() {
413 		logger.warning("destroy() not implemented yet");
414 	}
415 
416 	public boolean delete() {
417 		// TODO: implement this
418 		logger.warning("Delete() not implemented yet");
419 		isDeleted = true;
420 		return true;
421 	}
422 
423 	public boolean undelete(boolean restoreProperties) {
424 		// TODO: implement this
425 		logger.warning("Undelete() not implemented yet");
426 		isDeleted = false;
427 		return true;
428 	}
429 
430 	public boolean isDeleted() {
431 		return isDeleted;
432 	}
433 
434 	@Override
435 	public String getDeletedProperty() {
436 		// Not relevant
437 		return null;
438 	}
439 
440 	public <T> void notifyChange(FIBProperty<T> parameter, T oldValue, T newValue) {
441 		if (requireChange(oldValue, newValue)) {
442 			if (getSelection().size() == 0) {
443 				if (defaultValue instanceof HasPropertyChangeSupport) {
444 					((HasPropertyChangeSupport) defaultValue).getPropertyChangeSupport().firePropertyChange(parameter.getName(), oldValue,
445 							newValue);
446 				}
447 			}
448 			else {
449 				for (FIBModelObject n : getSelection()) {
450 					S style = getStyle(n);
451 					if (style instanceof HasPropertyChangeSupport) {
452 						((HasPropertyChangeSupport) style).getPropertyChangeSupport().firePropertyChange(parameter.getName(), oldValue,
453 								newValue);
454 					}
455 				}
456 			}
457 		}
458 	}
459 
460 	public <T> void notifyChange(FIBProperty<T> parameter) {
461 		T currentValue = getPropertyValue(parameter);
462 		notifyChange(parameter, currentValue != null ? null : parameter.getDefaultValue(), currentValue);
463 	}
464 
465 	public <T> void notifyAttributeChange(FIBProperty<T> parameter) {
466 		notifyChange(parameter);
467 	}
468 
469 	public Object performSuperGetter(String propertyIdentifier) {
470 		// Not relevant
471 		return null;
472 	}
473 
474 	public void performSuperSetter(String propertyIdentifier, Object value) {
475 		// Not relevant
476 
477 	}
478 
479 	public void performSuperAdder(String propertyIdentifier, Object value) {
480 		// Not relevant
481 
482 	}
483 
484 	public void performSuperAdder(String propertyIdentifier, Object value, int index) {
485 		// Not relevant
486 
487 	}
488 
489 	public void performSuperRemover(String propertyIdentifier, Object value) {
490 		// Not relevant
491 
492 	}
493 
494 	public boolean performSuperDelete() {
495 		// Not relevant
496 		return false;
497 	}
498 
499 	public boolean performSuperDelete(Object... context) {
500 		// Not relevant
501 		return false;
502 	}
503 
504 	public boolean performSuperUndelete(boolean restoreProperties) {
505 		// Not relevant
506 		return false;
507 	}
508 
509 	public Object performSuperGetter(String propertyIdentifier, Class<?> modelEntityInterface) {
510 		// Not relevant
511 		return null;
512 	}
513 
514 	public void performSuperSetter(String propertyIdentifier, Object value, Class<?> modelEntityInterface) {
515 		// Not relevant
516 
517 	}
518 
519 	public void performSuperAdder(String propertyIdentifier, Object value, Class<?> modelEntityInterface) {
520 		// Not relevant
521 
522 	}
523 
524 	public void performSuperRemover(String propertyIdentifier, Object value, Class<?> modelEntityInterface) {
525 		// Not relevant
526 
527 	}
528 
529 	public void performSuperDelete(Class<?> modelEntityInterface) {
530 		// Not relevant
531 
532 	}
533 
534 	public void performSuperDelete(Class<?> modelEntityInterface, Object... context) {
535 		// Not relevant
536 
537 	}
538 
539 	public void performSuperSetModified(boolean modified) {
540 		// Not relevant
541 
542 	}
543 
544 	public Object performSuperFinder(String finderIdentifier, Object value) {
545 		// Not relevant
546 		return null;
547 	}
548 
549 	public Object performSuperFinder(String finderIdentifier, Object value, Class<?> modelEntityInterface) {
550 		// Not relevant
551 		return null;
552 	}
553 
554 	public boolean delete(Object... context) {
555 		// Not relevant
556 		return false;
557 	}
558 
559 	public boolean isSerializing() {
560 		// Not relevant
561 		return false;
562 	}
563 
564 	public boolean isDeserializing() {
565 		// Not relevant
566 		return false;
567 	}
568 
569 	public boolean isModified() {
570 		// Not relevant
571 		return false;
572 	}
573 
574 	public void setModified(boolean modified) {
575 		// Not relevant
576 
577 	}
578 
579 	public boolean equalsObject(Object obj) {
580 		// Not relevant
581 		return false;
582 	}
583 
584 	public Object cloneObject() {
585 		// Not relevant
586 		return null;
587 	}
588 
589 	public Object cloneObject(Object... context) {
590 		// Not relevant
591 		return null;
592 	}
593 
594 	public boolean isCreatedByCloning() {
595 		// Not relevant
596 		return false;
597 	}
598 
599 	public boolean isBeingCloned() {
600 		// Not relevant
601 		return false;
602 	}
603 
604 	@Override
605 	public Object clone() {
606 		// Not relevant
607 		return this;
608 	}
609 
610 	public boolean hasKey(String key) {
611 		if (key != null) {
612 			FIBProperty<?> parameter = FIBProperty.getFIBProperty(getInspectedStyleClass(), key);
613 			if ((parameter != null) && (getPropertyValue(parameter) != null)) {
614 				return true;
615 			}
616 		}
617 		return false;
618 	}
619 
620 	/**
621 	 * Return object matching supplied key, if this object responses to this key
622 	 * 
623 	 * @param key
624 	 * @return
625 	 */
626 	public Object objectForKey(String key) {
627 		FIBProperty<?> parameter = FIBProperty.getFIBProperty(getInspectedStyleClass(), key);
628 		if (parameter != null) {
629 			return getPropertyValue(parameter);
630 		}
631 		return null;
632 	}
633 
634 	/**
635 	 * Sets an object matching supplied key, if this object responses to this key
636 	 * 
637 	 * @param key
638 	 * @return
639 	 */
640 	public void setObjectForKey(Object value, String key) {
641 		FIBProperty<?> parameter = FIBProperty.getFIBProperty(getInspectedStyleClass(), key);
642 		Object oldValue = objectForKey(key);
643 		if (requireChange(oldValue, value)) {
644 			if (getSelection().size() == 0) {
645 				defaultValue.setObjectForKey(value, key);
646 			}
647 			else {
648 				CompoundEdit setValueEdit = startRecordEdit("Set " + key + " to " + value);
649 				for (FIBModelObject n : getSelection()) {
650 					S style = getStyle(n);
651 					if (style != null) {
652 						style.setObjectForKey(value, key);
653 					}
654 				}
655 				stopRecordEdit(setValueEdit);
656 			}
657 			storedPropertyValues.put(parameter, value);
658 			pcSupport.firePropertyChange(key, oldValue, value);
659 		}
660 	}
661 
662 	/**
663 	 * Return type of key/value pair identified by supplied key identifier
664 	 * 
665 	 * @param key
666 	 * @return
667 	 */
668 	public Type getTypeForKey(String key) {
669 		if (hasKey(key)) {
670 			if (FIBProperty.getFIBProperty(getInspectedStyleClass(), key) != null) {
671 				return FIBProperty.getFIBProperty(getInspectedStyleClass(), key).getType();
672 			}
673 		}
674 		return null;
675 	}
676 
677 }