001package org.javasimon;
002
003import org.javasimon.callback.Callback;
004import org.javasimon.callback.CompositeCallback;
005import org.javasimon.callback.CompositeCallbackImpl;
006import org.javasimon.clock.SimonClock;
007import org.javasimon.utils.SimonUtils;
008
009import java.lang.reflect.Constructor;
010import java.lang.reflect.InvocationTargetException;
011import java.util.ArrayList;
012import java.util.Collection;
013import java.util.Collections;
014import java.util.Map;
015import java.util.concurrent.ConcurrentHashMap;
016
017/**
018 * Implements fully functional {@link Manager} in the enabled state. Does not support
019 * {@link #enable()}/{@link #disable()} - for this use {@link SwitchingManager}.
020 *
021 * @author <a href="mailto:virgo47@gmail.com">Richard "Virgo" Richter</a>
022 */
023public final class EnabledManager implements Manager {
024
025        private UnknownSimon rootSimon;
026
027        private final Map<String, AbstractSimon> allSimons = new ConcurrentHashMap<>();
028
029        private final CompositeCallback callback = new CompositeCallbackImpl();
030
031        private final ManagerConfiguration configuration;
032
033        private final SimonClock clock;
034
035        /** Creates new enabled manager. */
036        public EnabledManager() {
037                this(SimonClock.SYSTEM);
038        }
039
040        public EnabledManager(SimonClock clock) {
041                this.clock = clock;
042                rootSimon = new UnknownSimon(ROOT_SIMON_NAME, this);
043                allSimons.put(ROOT_SIMON_NAME, rootSimon);
044                configuration = new ManagerConfiguration(this);
045                callback.initialize(this);
046        }
047
048        @Override
049        public Simon getSimon(String name) {
050                return allSimons.get(name);
051        }
052
053        @Override
054        public synchronized void destroySimon(String name) {
055                if (name.equals(ROOT_SIMON_NAME)) {
056                        throw new SimonException("Root Simon cannot be destroyed!");
057                }
058                AbstractSimon simon = allSimons.remove(name);
059                if (simon.getChildren().size() > 0) {
060                        replaceUnknownSimon(simon, UnknownSimon.class);
061                } else {
062                        ((AbstractSimon) simon.getParent()).replaceChild(simon, null);
063                }
064                callback.onSimonDestroyed(simon);
065        }
066
067        @Override
068        public synchronized void clear() {
069                allSimons.clear();
070                rootSimon = new UnknownSimon(ROOT_SIMON_NAME, this);
071                allSimons.put(ROOT_SIMON_NAME, rootSimon);
072                callback.onManagerClear();
073        }
074
075        @Override
076        public Counter getCounter(String name) {
077                return (Counter) getOrCreateSimon(name, CounterImpl.class);
078        }
079
080        @Override
081        public Stopwatch getStopwatch(String name) {
082                return (Stopwatch) getOrCreateSimon(name, StopwatchImpl.class);
083        }
084
085        @Override
086        public Simon getRootSimon() {
087                return rootSimon;
088        }
089
090        @Override
091        public Collection<String> getSimonNames() {
092                return Collections.unmodifiableCollection(allSimons.keySet());
093        }
094
095        @SuppressWarnings({"unchecked"})
096        @Override
097        public Collection<Simon> getSimons(SimonFilter simonFilter) {
098                if (simonFilter == null) {
099                        return Collections.unmodifiableCollection((Collection) allSimons.values());
100                }
101                Collection<Simon> simons = new ArrayList<>();
102                for (AbstractSimon simon : allSimons.values()) {
103                        if (simonFilter.accept(simon)) {
104                                simons.add(simon);
105                        }
106                }
107                return simons;
108        }
109
110        private Simon getOrCreateSimon(String name, Class<? extends AbstractSimon> simonClass) {
111                if (name == null) {
112                        // create an "anonymous" Simon - Manager does not care about it anymore
113                        return instantiateSimon(null, simonClass);
114                }
115                if (name.equals(ROOT_SIMON_NAME)) {
116                        throw new SimonException("Root Simon cannot be replaced or recreated!");
117                }
118                AbstractSimon simon = allSimons.get(name);
119                if (simon != null && simonClass.isInstance(simon)) {
120                        return simon;
121                } else if (simon != null && !(simon instanceof UnknownSimon)) {
122                        throw new SimonException("Simon named '" + name + "' already exists and its type is '" +
123                                simon.getClass().getName() + "' while requested type is '" + simonClass.getName() + "'.");
124                } else {
125                        return createSimon(name, simonClass);
126                }
127        }
128
129        /**
130         * Even with ConcurrentHashMap we want to synchronize here, so newly created Simons can be fully
131         * set up with {@link Callback#onSimonCreated(Simon)}. ConcurrentHashMap still works fine for
132         * listing Simons, etc.
133         */
134        private synchronized Simon createSimon(String name, Class<? extends AbstractSimon> simonClass) {
135                AbstractSimon simon = allSimons.get(name);
136                if (simon == null) {
137                        SimonUtils.validateSimonName(name);
138                        simon = newSimon(name, simonClass);
139                } else if (simon instanceof UnknownSimon) {
140                        simon = replaceUnknownSimon(simon, simonClass);
141                }
142                callback.onSimonCreated(simon);
143                return simon;
144        }
145
146        // called from synchronized method
147        private AbstractSimon replaceUnknownSimon(AbstractSimon simon, Class<? extends AbstractSimon> simonClass) {
148                AbstractSimon newSimon = instantiateSimon(simon.getName(), simonClass);
149                newSimon.enabled = simon.enabled;
150
151                // fixes parent link and parent's children list
152                ((AbstractSimon) simon.getParent()).replaceChild(simon, newSimon);
153
154                // fixes children list and all children's parent link
155                for (Simon child : simon.getChildren()) {
156                        newSimon.addChild((AbstractSimon) child);
157                        ((AbstractSimon) child).setParent(newSimon);
158                }
159
160                allSimons.put(simon.getName(), newSimon);
161                return newSimon;
162        }
163
164        // called from synchronized method
165        private AbstractSimon newSimon(String name, Class<? extends AbstractSimon> simonClass) {
166                AbstractSimon simon = instantiateSimon(name, simonClass);
167                if (name != null) {
168                        addToHierarchy(simon, name);
169                        SimonConfiguration config = configuration.getConfig(name);
170                        if (config.getState() != null) {
171                                simon.setState(config.getState(), false);
172                        }
173                        allSimons.put(name, simon);
174                }
175                return simon;
176        }
177
178        private AbstractSimon instantiateSimon(String name, Class<? extends AbstractSimon> simonClass) {
179                AbstractSimon simon;
180                try {
181                        Constructor<? extends AbstractSimon> constructor = simonClass.getDeclaredConstructor(String.class, Manager.class);
182                        simon = constructor.newInstance(name, this);
183                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
184                        throw new SimonException(e);
185                }
186                return simon;
187        }
188
189        private void addToHierarchy(AbstractSimon simon, String name) {
190                int ix = name.lastIndexOf(HIERARCHY_DELIMITER);
191                AbstractSimon parent = rootSimon;
192                if (ix != -1) {
193                        String parentName = name.substring(0, ix);
194                        parent = allSimons.get(parentName);
195                        if (parent == null) {
196                                parent = new UnknownSimon(parentName, this);
197                                addToHierarchy(parent, parentName);
198                                allSimons.put(parentName, parent);
199                        }
200                }
201                parent.addChild(simon);
202        }
203
204        @Override
205        public CompositeCallback callback() {
206                return callback;
207        }
208
209        @Override
210        public ManagerConfiguration configuration() {
211                return configuration;
212        }
213
214        /** Throws {@link UnsupportedOperationException}. */
215        @Override
216        public void enable() {
217                throw new UnsupportedOperationException("Only SwitchingManager supports this operation.");
218        }
219
220        /** Throws {@link UnsupportedOperationException}. */
221        @Override
222        public void disable() {
223                throw new UnsupportedOperationException("Only SwitchingManager supports this operation.");
224        }
225
226        /**
227         * Returns true.
228         *
229         * @return true
230         */
231        @Override
232        public boolean isEnabled() {
233                return true;
234        }
235
236        @Override
237        public void message(String message) {
238                callback.onManagerMessage(message);
239        }
240
241        @Override
242        public void warning(String warning, Exception cause) {
243                callback.onManagerWarning(warning, cause);
244        }
245
246        @Override
247        public long nanoTime() {
248                return clock.nanoTime();
249        }
250
251        @Override
252        public long milliTime() {
253                return clock.milliTime();
254        }
255
256        @Override
257        public long millisForNano(long nanos) {
258                return clock.millisForNano(nanos);
259        }
260
261        synchronized void purgeIncrementalSimonsOlderThan(long thresholdMs) {
262                for (Simon simon : allSimons.values()) {
263                        if (simon instanceof AbstractSimon) {
264                                AbstractSimon abstractSimon = (AbstractSimon) simon;
265                                abstractSimon.purgeIncrementalSimonsOlderThan(thresholdMs);
266                        }
267                }
268        }
269}