DefaultObservableContext.java
001 /*
002  * Copyright 2008-2016 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package org.codehaus.griffon.runtime.core;
017 
018 import griffon.core.Context;
019 import griffon.core.ObservableContext;
020 import griffon.util.TypeUtils;
021 
022 import javax.annotation.Nonnull;
023 import javax.annotation.Nullable;
024 import java.util.List;
025 import java.util.concurrent.CopyOnWriteArrayList;
026 
027 import static java.util.Objects.requireNonNull;
028 
029 /**
030  @author Andres Almiray
031  @since 2.5.0
032  */
033 public class DefaultObservableContext extends DefaultContext implements ObservableContext {
034     private static final String ERROR_LISTENER_NULL = "Argument 'listener' must not be null";
035     private final List<ContextEventListener> listeners = new CopyOnWriteArrayList<>();
036 
037     private final ContextEventListener parentListener = new ContextEventListener() {
038         @Override
039         public void contextChanged(@Nonnull ContextEvent event) {
040             String key = event.getKey();
041             if (!hasKey(key)) {
042                 fireContextEvent(event.getType(), key, event.getOldValue(), event.getNewValue());
043             }
044         }
045     };
046 
047     public DefaultObservableContext() {
048         super();
049     }
050 
051     public DefaultObservableContext(@Nonnull Context parentContext) {
052         super(parentContext);
053         if (parentContext instanceof ObservableContext) {
054             ObservableContext observableParent = (ObservableContextparentContext;
055             observableParent.addContextEventListener(parentListener);
056         }
057     }
058 
059     @Override
060     public void addContextEventListener(@Nonnull ContextEventListener listener) {
061         requireNonNull(listener, ERROR_LISTENER_NULL);
062         if (!listeners.contains(listener)) listeners.add(listener);
063     }
064 
065     @Override
066     public void removeContextEventListener(@Nonnull ContextEventListener listener) {
067         requireNonNull(listener, ERROR_LISTENER_NULL);
068         listeners.remove(listener);
069     }
070 
071     @Nonnull
072     @Override
073     public ContextEventListener[] getContextEventListeners() {
074         return listeners.toArray(new ContextEventListener[listeners.size()]);
075     }
076 
077     @Override
078     public void put(@Nonnull String key, @Nullable Object value) {
079         boolean localKey = hasKey(key);
080         boolean parentKey = !localKey && containsKey(key);
081         Object oldValue = get(key);
082         super.put(key, value);
083         boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
084 
085         if (parentKey) {
086             if (!valuesAreEqualfireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value);
087         else {
088             if (localKey) {
089                 fireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value);
090             else {
091                 fireContextEvent(ContextEvent.Type.ADD, key, null, value);
092             }
093         }
094     }
095 
096     @Nullable
097     @Override
098     public Object remove(@Nonnull String key) {
099         boolean localKey = hasKey(key);
100         Object oldValue = super.remove(key);
101         boolean localKeyRemoved = localKey && !hasKey(key);
102         boolean containsKey = containsKey(key);
103 
104         try {
105             return oldValue;
106         finally {
107             if (localKeyRemoved) {
108                 if (containsKey) {
109                     Object value = get(key);
110                     boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
111                     if (!valuesAreEqualfireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value);
112                 else {
113                     fireContextEvent(ContextEvent.Type.REMOVE, key, oldValue, null);
114                 }
115             }
116         }
117     }
118 
119     @Nullable
120     @Override
121     public <T> T removeAs(@Nonnull String key) {
122         boolean localKey = hasKey(key);
123         T oldValue = super.removeAs(key);
124         boolean localKeyRemoved = localKey && !hasKey(key);
125         boolean containsKey = containsKey(key);
126 
127         try {
128             return oldValue;
129         finally {
130             if (localKeyRemoved) {
131                 if (containsKey) {
132                     T value = getAs(key);
133                     boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
134                     if (!valuesAreEqualfireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value);
135                 else {
136                     fireContextEvent(ContextEvent.Type.REMOVE, key, oldValue, null);
137                 }
138             }
139         }
140     }
141 
142     @Nullable
143     @Override
144     public <T> T removeConverted(@Nonnull String key, @Nonnull Class<T> type) {
145         boolean localKey = hasKey(key);
146         T oldValue = super.removeConverted(key, type);
147         boolean localKeyRemoved = localKey && !hasKey(key);
148         boolean containsKey = containsKey(key);
149 
150         try {
151             return oldValue;
152         finally {
153             if (localKeyRemoved) {
154                 if (containsKey) {
155                     T value = getConverted(key, type);
156                     boolean valuesAreEqual = TypeUtils.equals(oldValue, value);
157                     if (!valuesAreEqualfireContextEvent(ContextEvent.Type.UPDATE, key, oldValue, value);
158                 else {
159                     fireContextEvent(ContextEvent.Type.REMOVE, key, oldValue, null);
160                 }
161             }
162         }
163     }
164 
165     @Override
166     public void destroy() {
167         if (getParentContext() instanceof ObservableContext) {
168             ObservableContext observableParent = (ObservableContextgetParentContext();
169             observableParent.removeContextEventListener(parentListener);
170         }
171         listeners.clear();
172         super.destroy();
173     }
174 
175     protected void fireContextEvent(@Nonnull ContextEvent.Type type, @Nonnull String key, @Nullable Object oldValue, @Nullable Object newValue) {
176         fireContextEvent(new ContextEvent(type, key, oldValue, newValue));
177     }
178 
179     protected void fireContextEvent(@Nonnull ContextEvent event) {
180         for (ContextEventListener listener : listeners) {
181             listener.contextChanged(event);
182         }
183     }
184 }