001package org.javasimon;
002
003import org.javasimon.utils.SimonUtils;
004
005/**
006 * Matches Simon name patterns from configuration. Patterns can contain wildcard (*) that
007 * can be placed in following fashions:
008 * <ul>
009 * <li>empty string matches only {@link Manager#ROOT_SIMON_NAME} (root Simon)
010 * <li>{@code *} - matches anything
011 * <li>{@code something} - matches exactly {@code something}
012 * <li>{@code *something} - matches if tested name ends with {@code something}
013 * <li>{@code something*} - matches if tested name starts with {@code something}
014 * <li>{@code *something*} - matches if tested name contains {@code something} anywhere
015 * <li>{@code something*else} - matches if tested name starts with {@code something} and ends with {@code else}
016 * <li>anything else will throw {@link SimonException} with the message about invalid Simon pattern
017 * </ul>
018 * Without wildcard exact match is required. Every pattern with wildcards always matches with the same string
019 * without wildcards (in other words - wildcards matches with nothing as well).
020 */
021public final class SimonPattern implements SimonFilter {
022
023        private static final String WILDCARD_STAR = "*";
024
025        /** Expected Simon type. */
026        private final Class<? extends Simon> expectedType;
027
028        /** Original pattern from the configuration. */
029        private String pattern;
030
031        /** Used if complete match is expected. */
032        private String all;
033
034        /** Used if head should match. */
035        private String start;
036
037        /** Used if tail should match. */
038        private String end;
039
040        /** Used if anything inside (or everything) should match. */
041        private String middle;
042
043        private static final String INVALID_PATTERN = "Invalid Simon pattern: ";
044
045        /**
046         * Factory method that creates Simon name pattern - or returns {@code null} if parameter is {@code null}.
047         *
048         * @param pattern Simon name pattern as string
049         * @return Simon name pattern or {@code null} if pattern parameter is {@code null}
050         */
051        public static SimonPattern create(String pattern) {
052                if (pattern == null) {
053                        return null;
054                }
055
056                return createForType(pattern, Simon.class);
057        }
058
059        private static SimonPattern createForType(String pattern, Class<? extends Simon> expectedType) {
060                if (pattern == null) {
061                        return new SimonPattern("*", expectedType);
062                }
063                return new SimonPattern(pattern, expectedType);
064        }
065
066        /**
067         * Factory method that creates Counter name pattern - or returns a pattern
068         * that accepts all Counters if parameter is {@code null}.
069         *
070         * @param pattern Counter name pattern as string
071         * @return Counter name pattern
072         */
073        public static SimonPattern createForCounter(String pattern) {
074                return createForType(pattern, Counter.class);
075        }
076
077        /**
078         * Factory method that creates Stopwatch name pattern - or returns a pattern
079         * that accepts all Stopwatches if parameter is {@code null}.
080         *
081         * @param pattern Stopwatch name pattern as string
082         * @return Stopwatch name pattern
083         */
084        public static SimonPattern createForStopwatch(String pattern) {
085                return createForType(pattern, Stopwatch.class);
086        }
087
088        /**
089         * Creates Simon name pattern used to match config file entries.
090         *
091         * @param pattern Simon name pattern
092         * @throws SimonException if pattern is not valid (runtime exception)
093         */
094        public SimonPattern(String pattern) {
095                this(pattern, Simon.class);
096        }
097
098        /**
099         * Creates Simon pattern that used to match Simons of specified type with names that correspond to
100         * the pattern.
101         *
102         * @param pattern Simon name pattern
103         * @param expectedType expected type of Simon
104         * @throws SimonException if pattern is not valid (runtime exception)
105         */
106        public SimonPattern(String pattern, Class<? extends Simon> expectedType) {
107                this.expectedType = expectedType;
108                this.pattern = pattern;
109
110                if (!pattern.contains(WILDCARD_STAR)) {
111                        // no wildcard, we're going for complete match (all)
112                        all = pattern;
113                        validatePattern(all, pattern);
114                        return;
115                }
116                if (pattern.equals(WILDCARD_STAR)) {
117                        middle = ""; // everything contains this
118                        return;
119                }
120                if (pattern.startsWith(WILDCARD_STAR) && pattern.endsWith(WILDCARD_STAR) && pattern.length() > 2) {
121                        middle = pattern.substring(1, pattern.length() - 2);
122                        validatePattern(middle, pattern);
123                        return;
124                }
125
126                int ix = pattern.lastIndexOf('*');
127                if (ix != pattern.indexOf('*')) {
128                        throw new SimonException(INVALID_PATTERN + pattern);
129                }
130                if (!pattern.endsWith(WILDCARD_STAR)) {
131                        end = pattern.substring(ix + 1);
132                        validatePattern(end, pattern);
133                }
134                if (!pattern.startsWith(WILDCARD_STAR)) {
135                        start = pattern.substring(0, ix);
136                        validatePattern(start, pattern);
137                }
138        }
139
140        private void validatePattern(String simonNamePart, String pattern) {
141                if (!SimonUtils.checkName(simonNamePart)) {
142                        throw new SimonException(INVALID_PATTERN + pattern);
143                }
144        }
145
146        @Override
147        /**
148         * Checks if Simon's name matches this pattern.
149         *
150         * @param simon Simon to be tested
151         * @return true if Simon's name matches this pattern
152         */
153        public boolean accept(Simon simon)
154        {
155                return isCorrectType(simon) && matches(simon.getName());
156        }
157
158        private boolean isCorrectType(Simon simon) {
159                return expectedType.isInstance(simon);
160        }
161
162        /**
163         * Checks if Simon name matches this pattern.
164         *
165         * @param name tested name
166         * @return true if tested pattern matches this pattern
167         */
168        public boolean matches(String name) {
169                if (name == null) {
170                        return pattern.equals("");
171                }
172                if (all != null) {
173                        return all.equals(name);
174                }
175                if (middle != null) {
176                        return name.contains(middle);
177                }
178                if (start != null && !name.startsWith(start)) {
179                        return false;
180                }
181
182                return end == null || name.endsWith(end);
183        }
184
185        @Override
186        public boolean equals(Object o) {
187                if (this == o) {
188                        return true;
189                }
190                if (o == null || getClass() != o.getClass()) {
191                        return false;
192                }
193
194                SimonPattern that = (SimonPattern) o;
195
196                if (pattern != null ? !pattern.equals(that.pattern) : that.pattern != null) {
197                        return false;
198                }
199
200                if (expectedType != null ? !expectedType.equals(that.expectedType) : that.expectedType != null) {
201                        return false;
202                }
203
204                return true;
205        }
206
207        @Override
208        public int hashCode() {
209                int result = expectedType != null ? expectedType.hashCode() : 0;
210                result = 31 * result + (pattern != null ? pattern.hashCode() : 0);
211                return result;
212        }
213
214        @Override
215        public String toString() {
216                return "SimonPattern {" +
217                        "pattern='" + pattern + '\'' +
218                        ", expectedType=" + expectedType +
219                        '}';
220        }
221}