001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.extensions.markup.html.form.palette.component;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import java.util.TreeSet;
028
029import org.apache.wicket.WicketRuntimeException;
030import org.apache.wicket.extensions.markup.html.form.palette.Palette;
031import org.apache.wicket.markup.html.form.HiddenField;
032import org.apache.wicket.markup.html.form.IChoiceRenderer;
033import org.apache.wicket.model.Model;
034import org.apache.wicket.util.string.Strings;
035
036
037/**
038 * Component to keep track of selections on the html side. Also used for encoding and decoding those
039 * selections between html and java.
040 * 
041 * @param <T>
042 *            Type of the palette
043 * 
044 * @author Igor Vaynberg ( ivaynberg )
045 */
046public class Recorder<T> extends HiddenField<String>
047{
048        private static final long serialVersionUID = 1L;
049
050        /** parent palette object */
051        private final Palette<T> palette;
052
053        /**
054         * @param id
055         *            component id
056         * @param palette
057         *            parent palette object
058         */
059        public Recorder(final String id, final Palette<T> palette)
060        {
061                super(id, new Model<String>());
062
063                this.palette = palette;
064                setOutputMarkupId(true);
065        }
066
067        /**
068         * @return parent Palette object
069         */
070        public Palette<T> getPalette()
071        {
072                return palette;
073        }
074
075        /**
076         * 
077         * @see org.apache.wicket.markup.html.form.AbstractTextComponent#onBeforeRender()
078         */
079        @Override
080        protected void onBeforeRender()
081        {
082                super.onBeforeRender();
083
084                initIds();
085        }
086
087        /**
088         * Synchronize the ids in this' model from the palette's model.
089         */
090        private void initIds()
091        {
092                // construct the model string based on selection collection
093                IChoiceRenderer<? super T> renderer = getPalette().getChoiceRenderer();
094                StringBuilder modelStringBuffer = new StringBuilder();
095                Collection<T> modelCollection = getPalette().getModelCollection();
096                if (modelCollection == null)
097                {
098                        throw new WicketRuntimeException(
099                                "Expected getPalette().getModelCollection() to return a non-null value."
100                                        + " Please make sure you have model object assigned to the palette");
101                }
102                Iterator<T> selection = modelCollection.iterator();
103
104                int i = 0;
105                while (selection.hasNext())
106                {
107                        modelStringBuffer.append(renderer.getIdValue(selection.next(), i++));
108                        if (selection.hasNext())
109                        {
110                                modelStringBuffer.append(',');
111                        }
112                }
113
114                // set model and update ids array
115                String modelString = modelStringBuffer.toString();
116                setModelObject(modelString);
117        }
118
119        /**
120         * Get the selected choices based on the palette's available choices and the current model or
121         * input data entered by the user.
122         * 
123         * @return selected choices
124         * 
125         * @see #getValue()
126         */
127        public List<T> getSelectedList()
128        {
129                final IChoiceRenderer<? super T> renderer = getPalette().getChoiceRenderer();
130                final Collection<? extends T> choices = getPalette().getChoices();
131                final List<T> selected = new ArrayList<>(choices.size());
132
133                // reduce number of method calls by building a lookup table
134                final Map<T, String> idForChoice = new HashMap<>(choices.size());
135                for (final T choice : choices)
136                {
137                        idForChoice.put(choice, renderer.getIdValue(choice, 0));
138                }
139
140                for (final String id : Strings.split(getValue(), ','))
141                {
142                        for (final T choice : choices)
143                        {
144                                final String idValue = idForChoice.get(choice);
145                                if (id.equals(idValue)) // null-safe compare
146                                {
147                                        selected.add(choice);
148                                        break;
149                                }
150                        }
151                }
152
153                return selected;
154        }
155
156        /**
157         * Get the unselected choices based on the palette's available choices and the current model or
158         * input data entered by the user.
159         * 
160         * @return unselected choices
161         * 
162         * @see #getValue()
163         */
164        public List<T> getUnselectedList()
165        {
166                final IChoiceRenderer<? super T> renderer = getPalette().getChoiceRenderer();
167                final Collection<? extends T> choices = getPalette().getChoices();
168                final List<T> unselected = new ArrayList<>(choices.size());
169                final Set<String> ids = new TreeSet<>(Arrays.asList(Strings.split(getValue(), ',')));
170
171                for (final T choice : choices)
172                {
173                        final String choiceId = renderer.getIdValue(choice, 0);
174
175                        if (ids.contains(choiceId) == false)
176                        {
177                                unselected.add(choice);
178                        }
179                }
180
181                return unselected;
182        }
183}