package org.netbeans.gradle.project.properties.standard; import java.io.File; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.jtrim.collections.CollectionsEx; import org.jtrim.property.PropertyFactory; import org.jtrim.property.PropertySource; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.project.api.config.ConfigPath; import org.netbeans.gradle.project.api.config.ConfigTree; import org.netbeans.gradle.project.api.config.PropertyDef; import org.netbeans.gradle.project.api.config.PropertyKeyEncodingDef; import org.netbeans.gradle.project.api.config.PropertyValueDef; import org.netbeans.gradle.project.api.config.ValueMerger; import org.netbeans.gradle.project.api.config.ValueReference; public final class CommonProperties { private static final Logger LOGGER = Logger.getLogger(EnumKeyEncodingDef.class.getName()); private static final String SAVE_FILE_NAME_SEPARATOR = "/"; @SuppressWarnings("unchecked") public static <T> PropertyValueDef<T, T> getIdentityValueDef() { return (PropertyValueDef<T, T>)IdentityValueDef.INSTANCE; } @SuppressWarnings("unchecked") public static <T> ValueMerger<T> getParentIfNullValueMerger() { return (ValueMerger<T>)ParentIfNullValueMerger.INSTANCE; } public static PropertyKeyEncodingDef<String> getIdentityKeyEncodingDef() { return IdentityKeyEncodingDef.INSTANCE; } public static <T extends Enum<T>> PropertyKeyEncodingDef<T> enumKeyEncodingDef(final Class<T> enumType) { return new EnumKeyEncodingDef<>(enumType); } public static PropertyKeyEncodingDef<Integer> intKeyEncodingDef() { return IntKeyEncodingDef.INSTANCE; } public static PropertyKeyEncodingDef<List<String>> stringListEncodingDef() { return StringListKeyEncodingDef.DEFAULT; } public static PropertyKeyEncodingDef<List<String>> stringListEncodingDef(String entryName) { return new StringListKeyEncodingDef(entryName); } public static PropertyKeyEncodingDef<ConfigTree> getIdentityTreeKeyEncodingDef() { return IdentityTreeKeyEncodingDef.INSTANCE; } public static <T> ValueMerger<List<T>> listValueMerger() { return new ListValueMerger<>(); } public static Path tryReadFilePath(String normalizedPath) { if (normalizedPath == null) { return null; } String separator = FileSystems.getDefault().getSeparator(); String nativePath = normalizedPath.replace(SAVE_FILE_NAME_SEPARATOR, separator); return Paths.get(nativePath); } public static String normalizeFilePath(Path file) { String result = file.toString(); String separator = file.getFileSystem().getSeparator(); return result.replace(separator, SAVE_FILE_NAME_SEPARATOR); } public static PropertyDef<?, File> defineFileProperty(String... keyPath) { PropertyDef.Builder<String, File> result = new PropertyDef.Builder<>(ConfigPath.fromKeys(keyPath)); result.setValueDef(FileValueDef.INSTANCE); result.setKeyEncodingDef(getIdentityKeyEncodingDef()); return result.create(); } public static <T extends Enum<T>> PropertyDef<?, T> defineEnumProperty(Class<T> enumType, String... keyPath) { PropertyDef.Builder<T, T> result = new PropertyDef.Builder<>(ConfigPath.fromKeys(keyPath)); result.setValueDef(CommonProperties.<T>getIdentityValueDef()); result.setKeyEncodingDef(enumKeyEncodingDef(enumType)); return result.create(); } public static PropertyDef<?, Boolean> defineBooleanProperty(String... keyPath) { PropertyDef.Builder<Boolean, Boolean> result = new PropertyDef.Builder<>(ConfigPath.fromKeys(keyPath)); result.setValueDef(CommonProperties.<Boolean>getIdentityValueDef()); result.setKeyEncodingDef(BooleanKeyEncodingDef.INSTANCE); return result.create(); } public static PropertyDef<?, Integer> defineIntProperty(String... keyPath) { PropertyDef.Builder<Integer, Integer> result = new PropertyDef.Builder<>(ConfigPath.fromKeys(keyPath)); result.setValueDef(CommonProperties.<Integer>getIdentityValueDef()); result.setKeyEncodingDef(IntKeyEncodingDef.INSTANCE); return result.create(); } public static PropertyDef<?, String> defineStringProperty(String... keyPath) { PropertyDef.Builder<String, String> result = new PropertyDef.Builder<>(ConfigPath.fromKeys(keyPath)); result.setValueDef(CommonProperties.<String>getIdentityValueDef()); result.setKeyEncodingDef(getIdentityKeyEncodingDef()); return result.create(); } public static PropertyDef<?, List<String>> defineStringListProperty(String... keyPath) { PropertyDef.Builder<List<String>, List<String>> result = new PropertyDef.Builder<>(ConfigPath.fromKeys(keyPath)); result.setValueDef(CommonProperties.<List<String>>getIdentityValueDef()); result.setKeyEncodingDef(stringListEncodingDef()); result.setValueMerger(CommonProperties.<String>listValueMerger()); return result.create(); } private enum IdentityTreeKeyEncodingDef implements PropertyKeyEncodingDef<ConfigTree> { INSTANCE; @Override public ConfigTree decode(ConfigTree config) { return config; } @Override public ConfigTree encode(ConfigTree value) { return value; } } private enum ParentIfNullValueMerger implements ValueMerger<Object> { INSTANCE; @Override public Object mergeValues(Object child, ValueReference<Object> parent) { return child != null ? child : parent.getValue(); } } private enum IdentityValueDef implements PropertyValueDef<Object, Object> { INSTANCE; @Override public PropertySource<Object> property(Object valueKey) { return PropertyFactory.constSource(valueKey); } @Override public Object getKeyFromValue(Object value) { return value; } } private enum IdentityKeyEncodingDef implements PropertyKeyEncodingDef<String> { INSTANCE; @Override public String decode(ConfigTree config) { return config.getValue(null); } @Override public ConfigTree encode(String value) { return ConfigTree.singleValue(value); } } private enum BooleanKeyEncodingDef implements PropertyKeyEncodingDef<Boolean> { INSTANCE; private static final String TRUE_STR = Boolean.TRUE.toString(); private static final String FALSE_STR = Boolean.FALSE.toString(); @Override public Boolean decode(ConfigTree config) { String strValue = config.getValue(null); if (TRUE_STR.equalsIgnoreCase(strValue)) { return true; } if (FALSE_STR.equalsIgnoreCase(strValue)) { return false; } return null; } @Override public ConfigTree encode(Boolean value) { return ConfigTree.singleValue(value.toString()); } } private static final class StringListKeyEncodingDef implements PropertyKeyEncodingDef<List<String>> { public static final StringListKeyEncodingDef DEFAULT = new StringListKeyEncodingDef("entry"); private final String entryName; public StringListKeyEncodingDef(String entryName) { ExceptionHelper.checkNotNullArgument(entryName, "entryName"); this.entryName = entryName; } @Override public List<String> decode(ConfigTree config) { List<ConfigTree> entries = config.getChildTrees(entryName); int entryCount = entries.size(); if (entryCount == 0) { return Collections.emptyList(); } List<String> result = new ArrayList<>(entryCount); for (ConfigTree entry: entries) { result.add(entry.getValue("")); } return Collections.unmodifiableList(result); } @Override public ConfigTree encode(List<String> value) { if (value.isEmpty()) { return ConfigTree.EMPTY; } ConfigTree.Builder result = new ConfigTree.Builder(); for (String entry: value) { result.addChildBuilder(entryName).setValue(entry); } return result.create(); } } private enum IntKeyEncodingDef implements PropertyKeyEncodingDef<Integer> { INSTANCE; @Override public Integer decode(ConfigTree config) { String strValue = config.getValue("").trim(); if (strValue.isEmpty()) { return null; } try { return Integer.parseInt(strValue); } catch (NumberFormatException ex) { return null; } } @Override public ConfigTree encode(Integer value) { return ConfigTree.singleValue(value.toString()); } } private static final class EnumKeyEncodingDef<T extends Enum<T>> implements PropertyKeyEncodingDef<T> { private final Class<T> enumType; private final Map<String, T> byNameValues; public EnumKeyEncodingDef(Class<T> enumType) { ExceptionHelper.checkNotNullArgument(enumType, "enumType"); this.enumType = enumType; T[] allValues = enumType.getEnumConstants(); byNameValues = CollectionsEx.newHashMap(allValues.length); for (T value: allValues) { byNameValues.put(value.name().toUpperCase(Locale.ROOT), value); } } @Override public T decode(ConfigTree config) { String strValue = config.getValue("").trim(); if (strValue.isEmpty()) { return null; } T result = byNameValues.get(strValue.toUpperCase(Locale.ROOT)); if (result == null) { LOGGER.log(Level.INFO, "Illegal enum value for config: {0} expected an instance of {1}", new Object[]{strValue, enumType.getSimpleName()}); } return result; } @Override public ConfigTree encode(T value) { return ConfigTree.singleValue(value.name()); } } private enum FileValueDef implements PropertyValueDef<String, File> { INSTANCE; @Override public PropertySource<File> property(String valueKey) { return PropertyFactory.constSource(valueKey != null ? new File(valueKey) : null); } @Override public String getKeyFromValue(File value) { return value != null ? value.getPath() : null; } } private static class ListValueMerger<T> implements ValueMerger<List<T>> { public ListValueMerger() { } @Override public List<T> mergeValues(List<T> child, ValueReference<List<T>> parent) { List<T> parentValue = parent.getValue(); if (parentValue == null || parentValue.isEmpty()) { return child; } if (child == null || child.isEmpty()) { return parentValue; } List<T> result = new ArrayList<>(child.size() + parentValue.size()); result.addAll(child); result.addAll(parentValue); return result; } } private CommonProperties() { throw new AssertionError(); } }