001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.plugins.transfer;
019
020import java.lang.reflect.Field;
021import java.util.List;
022
023import org.simpleframework.xml.Element;
024import org.syncany.util.ReflectionUtil;
025
026import com.google.common.collect.ImmutableList;
027import com.google.common.collect.Lists;
028import com.google.common.collect.Ordering;
029import com.google.common.primitives.Ints;
030
031/**
032 * Helper class to read the options of a {@link TransferSettings} using the
033 * {@link Setup} and {@link Element} annotations.
034 *
035 * @author Christian Roth (christian.roth@port17.de)
036 */
037public class TransferPluginOptions {
038        private static final int MAX_NESTED_LEVELS = 3;
039
040        /**
041         * Get an ordered list of {@link TransferPluginOption}s, given class a {@link TransferSettings} class.
042         *
043         * <p>This method uses the {@link Setup} and {@link Element} annotation, and their attributes
044         * to sort the options. If no annotation is given or no order attribute is provided, the
045         * option will be listed last.
046         */
047        public static List<TransferPluginOption> getOrderedOptions(Class<? extends TransferSettings> transferSettingsClass) {
048                return getOrderedOptions(transferSettingsClass, 0);
049        }
050
051        private static List<TransferPluginOption> getOrderedOptions(Class<? extends TransferSettings> transferSettingsClass, int level) {
052                List<Field> fields = getOrderedFields(transferSettingsClass);
053                ImmutableList.Builder<TransferPluginOption> options = ImmutableList.builder();
054
055                for (Field field : fields) {
056                        TransferPluginOption option = getOptionFromField(field, transferSettingsClass, level);
057                        options.add(option);
058                }
059
060                return options.build();
061        }
062
063        private static TransferPluginOption getOptionFromField(Field field, Class<? extends TransferSettings> transferSettingsClass, int level) {
064                Element elementAnnotation = field.getAnnotation(Element.class);
065                Setup setupAnnotation = field.getAnnotation(Setup.class);
066
067                boolean hasName = !elementAnnotation.name().equalsIgnoreCase("");
068                boolean hasDescription = setupAnnotation != null && !setupAnnotation.description().equals("");
069                boolean hasCallback = setupAnnotation != null && !setupAnnotation.callback().isInterface();
070                boolean hasConverter = setupAnnotation != null && !setupAnnotation.converter().isInterface();
071                boolean hasFileType = setupAnnotation != null && setupAnnotation.fileType() != null;
072
073                String name = (hasName) ? elementAnnotation.name() : field.getName();
074                String description = (hasDescription) ? setupAnnotation.description() : field.getName();
075                FileType fileType = (hasFileType) ? setupAnnotation.fileType() : null;
076                boolean required = elementAnnotation.required();
077                boolean sensitive = setupAnnotation != null && setupAnnotation.sensitive();
078                boolean singular = setupAnnotation != null && setupAnnotation.singular();
079                boolean visible = setupAnnotation != null && setupAnnotation.visible();
080                boolean encrypted = field.getAnnotation(Encrypted.class) != null;
081                Class<? extends TransferPluginOptionCallback> callback = (hasCallback) ? setupAnnotation.callback() : null;
082                Class<? extends TransferPluginOptionConverter> converter = (hasConverter) ? setupAnnotation.converter() : null;
083
084                boolean isNestedOption = TransferSettings.class.isAssignableFrom(field.getType());
085
086                if (isNestedOption) {
087                        return createNestedOption(field, level, name, description, fileType, encrypted, sensitive, singular, visible, required, callback, converter);
088                }
089                else {
090                        return createNormalOption(field, transferSettingsClass, name, description, fileType, encrypted, sensitive, singular, visible, required, callback, converter);
091                }
092        }
093
094        @SuppressWarnings("unchecked")
095        private static TransferPluginOption createNestedOption(Field field, int level, String name, String description, FileType fileType,
096                        boolean encrypted, boolean sensitive, boolean singular, boolean visible, boolean required,
097                        Class<? extends TransferPluginOptionCallback> callback, Class<? extends TransferPluginOptionConverter> converter) {
098
099                if (++level > MAX_NESTED_LEVELS) {
100                        throw new RuntimeException("Plugin uses too many nested transfer settings (max allowed value: " + MAX_NESTED_LEVELS + ")");
101                }
102
103                Class<? extends TransferSettings> fieldClass = (Class<? extends TransferSettings>) field.getType();
104                return new NestedTransferPluginOption(field, name, description, fieldClass, fileType, encrypted, sensitive, singular, visible, required, callback, converter,
105                                getOrderedOptions(fieldClass));
106        }
107
108        private static TransferPluginOption createNormalOption(Field field, Class<? extends TransferSettings> transferSettingsClass, String name,
109                        String description, FileType fileType, boolean encrypted, boolean sensitive, boolean singular, boolean visible, boolean required,
110                        Class<? extends TransferPluginOptionCallback> callback, Class<? extends TransferPluginOptionConverter> converter) {
111
112                return new TransferPluginOption(field, name, description, field.getType(), fileType, encrypted, sensitive, singular, visible, required, callback, converter);
113        }
114
115        private static List<Field> getOrderedFields(Class<? extends TransferSettings> transferSettingsClass) {
116                Ordering<Field> byOrderAnnotation = new Ordering<Field>() {
117                        @Override
118                        public int compare(Field leftField, Field rightField) {
119                                int leftOrderValue = (leftField.getAnnotation(Setup.class) != null) ? leftField.getAnnotation(Setup.class).order() : -1;
120                                int rightOrderValue = (rightField.getAnnotation(Setup.class) != null) ? rightField.getAnnotation(Setup.class).order() : -1;
121
122                                return Ints.compare(leftOrderValue, rightOrderValue);
123                        }
124                };
125
126                List<Field> fields = Lists.newArrayList(ReflectionUtil.getAllFieldsWithAnnotation(transferSettingsClass, Element.class));
127                return ImmutableList.copyOf(byOrderAnnotation.nullsLast().sortedCopy(fields));
128        }
129}