001package ball.util;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import ball.annotation.MatcherGroup;
022import ball.annotation.PatternRegex;
023import java.lang.annotation.AnnotationFormatError;
024import java.lang.reflect.Field;
025import java.lang.reflect.Method;
026import java.util.List;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029import org.apache.commons.lang3.reflect.FieldUtils;
030import org.apache.commons.lang3.reflect.MethodUtils;
031
032import static ball.util.Converter.convertTo;
033
034/**
035 * Interface providing default methods for beans classes annotated with
036 * {@link PatternRegex} and whose methods are annotated with
037 * {@link MatcherGroup}.
038 *
039 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
040 */
041public interface PatternMatcherBean {
042
043    /**
044     * Method to initialize fields and methods annotated with
045     * {@link MatcherGroup} with the results of parsing the {@code sequence}
046     * argument.
047     *
048     * @param   sequence        The {@link CharSequence} to parse.
049     *
050     * @throws  IllegalArgumentException
051     *                          If the {@link CharSequence} does not match
052     *                          the {@link PatternRegex#value()}.
053     */
054    default void initialize(CharSequence sequence) {
055        Matcher matcher = matcher(sequence);
056
057        if (! matcher.matches()) {
058            throw new IllegalArgumentException("\"" + String.valueOf(sequence)
059                                               + "\" does not match " + matcher.pattern().pattern());
060        }
061
062        try {
063            List<Field> fields = FieldUtils.getFieldsListWithAnnotation(getClass(), MatcherGroup.class);
064
065            for (Field field : fields) {
066                MatcherGroup group = field.getAnnotation(MatcherGroup.class);
067                String string = matcher.group(group.value());
068                Object value = (string != null) ? convertTo(string, field.getType()) : null;
069
070                FieldUtils.writeField(field, this, value, true);
071            }
072
073            List<Method> methods =
074                MethodUtils.getMethodsListWithAnnotation(getClass(), MatcherGroup.class, true, true);
075
076            for (Method method : methods) {
077                MatcherGroup group = method.getAnnotation(MatcherGroup.class);
078                Object value = convertTo(matcher.group(group.value()), method.getParameterTypes()[0]);
079
080                MethodUtils.invokeMethod(this, true,
081                                         method.getName(), new Object[] { value }, method.getParameterTypes());
082            }
083        } catch (IllegalAccessException exception) {
084            exception.printStackTrace(System.err);
085        } catch (RuntimeException exception) {
086            throw exception;
087        } catch (Exception exception) {
088            throw new IllegalArgumentException(String.valueOf(matcher), exception);
089        }
090    }
091
092    /**
093     * Method to get the {@link Matcher} for the argument input.
094     *
095     * @param   sequence        The {@link CharSequence} to parse.
096     *
097     * @return  The {@link Matcher} for the {@link #pattern()} applied to
098     *          the {@link CharSequence}.
099     */
100    default Matcher matcher(CharSequence sequence) {
101        return pattern().matcher(sequence);
102    }
103
104    /**
105     * Method to get the compiled {@link Pattern} for this annotated bean.
106     *
107     * @return  The {@link Pattern}.
108     */
109    default Pattern pattern() {
110        Pattern pattern = null;
111        PatternRegex annotation = getClass().getAnnotation(PatternRegex.class);
112
113        try {
114            pattern = Pattern.compile(annotation.value());
115        } catch (Exception exception) {
116            throw new AnnotationFormatError(annotation.toString(), exception);
117        }
118
119        return pattern;
120    }
121}