/*
 * Copyright 2016 Matti Tahvonen.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.vaadin.viritin.fields;

import com.vaadin.data.Property;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomField;
import java.lang.reflect.Method;
import java.util.Collection;
import org.apache.commons.lang3.StringUtils;

/**
 * A simple TextField based component to edit collections of objects, which can
 * be converted to String presentation and back.
 * <p>
 * Both split and joinSeparator can be specified and one can provide custom
 * strategies to convert from String presentation to element types.
 */
public class CommaSeparatedCollectionField extends CustomField<Collection> {

    public interface FromStringInstantiator<T> {

        T instance(String stringPresentation);
    }

    MTextField textField = new MTextField();
    private Collection collection;
    private String splitSeparatorRegexp = "\\s*,\\s*";
    private String joinSeparator = ", ";
    private Class<?> elementType;
    private FromStringInstantiator instantiator;

    public CommaSeparatedCollectionField() {
        textField.addValueChangeListener(new ValueChangeListener() {
            @Override
            public void valueChange(Property.ValueChangeEvent event) {
                if (textField.isUserValueChange()) {

                    String[] parts = textField.getValue().split(splitSeparatorRegexp);
                    // TODO null check for collection and create it if not set (e.g. null in property)
                    collection.clear();

                    for (String part : parts) {
                        if (instantiator != null) {
                            collection.add(instantiator.instance(part));
                        } else if (elementType != null) {

                            try {

                                Method declaredMethod = elementType.getDeclaredMethod("valueOf", String.class);
                                if (declaredMethod != null) {
                                    collection.add(declaredMethod.invoke(null, part));
                                } else {
                                    collection.add(elementType.getConstructor(String.class).newInstance(part));
                                }
                            } catch (Exception ex) {
                                throw new RuntimeException("The string " + part + " could not be converted to " + elementType.getSimpleName(), ex);
                            }
                        } else {
                            // assume String values in the collection
                            collection.add(part);
                        }
                    }
                }
                fireValueChange(true);
            }
        });
    }

    @Override
    protected Component initContent() {
        return textField;
    }

    @Override
    public Class<? extends Collection> getType() {
        return Collection.class;
    }

    @Override
    protected void setInternalValue(Collection newValue) {
        this.collection = newValue;

        String toPresentation = StringUtils.join(collection, joinSeparator);
        textField.setValue(toPresentation);
        super.setInternalValue(newValue);
    }

    public Class<?> getElementType() {
        return elementType;
    }

    /**
     * Sets the type of element in the collection. The class needs to have
     * either static valueOf(String) method or a constructor with String
     * parameter. Those will be used to instantiate objects from user input.
     * Alternatively you can specify an explicit instantiator.
     * <p>
     * If the element type in collection is String, no elmentType or
     * instantiator is needed.
     *
     * @param elementType the type of objects in the collection.
     */
    public void setElementType(Class<?> elementType) {
        this.elementType = elementType;
    }

    public void setInstantiator(FromStringInstantiator instantiator) {
        this.instantiator = instantiator;
    }

    public FromStringInstantiator getInstantiator() {
        return instantiator;
    }

    public String getJoinSeparator() {
        return joinSeparator;
    }

    /**
     * @param joinSeparator the separator string to be used when joining objects
     * to string presentation for editing. The default value is ", "
     */
    public void setJoinSeparator(String joinSeparator) {
        this.joinSeparator = joinSeparator;
    }

    public String getSplitSeparatorRegexp() {
        return splitSeparatorRegexp;
    }

    /**
     * Sets the split separator that is used to slice the input string into a
     * parts. The value is a regular expression so you can strip away extra
     * spaces. The default value is "\\s*,\\s*".
     *
     * @param splitSeparatorRegexp the split separator
     */
    public void setSplitSeparatorRegexp(String splitSeparatorRegexp) {
        this.splitSeparatorRegexp = splitSeparatorRegexp;
    }

}

@@ -790,16 +790,81 @@
 ection;%0A
+import java.util.logging.Level;%0Aimport java.util.logging.Logger;%0A
 import o
@@ -2148,16 +2148,22 @@
    for (
+final 
 String p
@@ -2173,24 +2173,24 @@
  : parts) %7B%0A
-
             
@@ -2540,19 +2540,30 @@
         
-if 
+collection.add
 (declare
@@ -2569,27 +2569,37 @@
 edMethod
- != null) %7B
+.invoke(null, part));
 %0A       
@@ -2623,66 +2623,44 @@
     
-        collection.add(declaredMethod.invoke(null, part));
+%7D catch (NoSuchMethodException ex) %7B
 %0A   
@@ -2680,38 +2680,35 @@
                 
-%7D else
+try
  %7B%0A             
@@ -2798,32 +2798,239 @@
 nstance(part));%0A
+                                %7D catch (Exception ex1) %7B%0A                                throw new RuntimeException(%22The string %22 + part + %22 could not be converted to %22 + elementType.getSimpleName(), ex1);%0A
                 

Fix to reflection magic