001/*
002 *  Copyright 2018 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.data.type;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021import java.util.Optional;
022import java.util.function.Function;
023import java.util.stream.Stream;
024
025import org.apache.cocoon.xml.AttributesImpl;
026import org.apache.commons.lang3.StringUtils;
027import org.apache.commons.lang3.tuple.ImmutableTriple;
028import org.apache.commons.lang3.tuple.Triple;
029import org.w3c.dom.Element;
030import org.xml.sax.ContentHandler;
031import org.xml.sax.SAXException;
032
033import org.ametys.core.model.type.AbstractElementType;
034import org.ametys.core.model.type.ModelItemTypeHelper;
035import org.ametys.plugins.repository.metadata.MultilingualString;
036import org.ametys.plugins.repository.metadata.MultilingualStringHelper;
037import org.ametys.runtime.model.ViewItem;
038import org.ametys.runtime.model.compare.DataChangeType;
039import org.ametys.runtime.model.compare.DataChangeTypeDetail;
040import org.ametys.runtime.model.exception.BadItemTypeException;
041import org.ametys.runtime.model.type.DataContext;
042
043/**
044 * Abstract class for multilingual string type of elements
045 */
046public abstract class AbstractMultilingualStringElementType extends AbstractElementType<MultilingualString>
047{
048    @Override
049    public MultilingualString convertValue(Object value) throws BadItemTypeException
050    {
051        if (value instanceof String)
052        {
053            try
054            {
055                return MultilingualStringHelper.fromString((String) value);
056            }
057            catch (IllegalArgumentException e)
058            {
059                throw new BadItemTypeException("Unable to cast '" + value + "' to a multilingual string");
060            }
061        }
062        
063        return super.convertValue(value);
064    }
065    
066    @Override
067    public String toString(MultilingualString value)
068    {
069        return MultilingualStringHelper.toString(value);
070    }
071
072    @SuppressWarnings("unchecked")
073    public Object fromJSONForClient(Object json, DataContext context)
074    {
075        if (json == null)
076        {
077            return json;
078        }
079        else if (json instanceof Map)
080        {
081            return MultilingualStringHelper.fromJSON((Map<String, Object>) json);
082        }
083        else if (json instanceof List)
084        {
085            List<MultilingualString> multilingualStrings = new ArrayList<>();
086            for (Map<String, Object> singleJson : (List<Map<String, Object>>) json)
087            {
088                multilingualStrings.add(MultilingualStringHelper.fromJSON(singleJson));
089            }
090            return multilingualStrings.toArray(new MultilingualString[multilingualStrings.size()]);
091        }
092        else
093        {
094            throw new IllegalArgumentException("Try to convert the non multilingual string JSON object '" + json + "' into a multilingual string");
095        }
096    }
097    
098    @Override
099    protected Object _singleValueToJSON(MultilingualString value, DataContext context)
100    {
101        return MultilingualStringHelper.toJson(value);
102    }
103    
104    @Override
105    protected MultilingualString _singleValueFromXML(Element element, Optional<Object> additionalData)
106    {
107        return MultilingualStringHelper.fromXML(element);
108    }
109    
110    @Override
111    protected void _singleValueToSAX(ContentHandler contentHandler, String tagName, MultilingualString value, Optional<ViewItem> viewItem, DataContext context, AttributesImpl attributes) throws SAXException
112    {
113        MultilingualStringHelper.sax(contentHandler, tagName, value, attributes, context.getLocale());
114    }
115    
116    @Override
117    protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareMultipleValues(MultilingualString[] value1, MultilingualString[] value2)
118    {
119        throw new UnsupportedOperationException("Unable to compare multiple values of type '" + getId() + "'");
120    }
121    
122    @Override
123    protected Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareSingleValues(MultilingualString value1, MultilingualString value2)
124    {
125        if (ModelItemTypeHelper.areSingleObjectsBothNotNullAndDifferents(value1, value2))
126        {
127            return Stream.of
128                    (
129                        // Manages modified and removed strings
130                        value1.getLocales()
131                            .stream()
132                            .map
133                            (
134                                locale -> ModelItemTypeHelper.compareSingleStrings
135                                (
136                                    value1.getValue(locale),
137                                    value2.hasLocale(locale) ? value2.getValue(locale) : StringUtils.EMPTY,
138                                    locale.toString()
139                                )
140                            )
141                            .filter(Optional::isPresent)
142                            .map(Optional::get),
143
144                        // Manages added strings
145                        value2.getLocales()
146                            .stream()
147                            .filter(locale -> !value1.hasLocale(locale))
148                            .map(locale -> new ImmutableTriple<>(DataChangeType.ADDED, DataChangeTypeDetail.NONE, locale.toString()))
149                    )
150                    .flatMap(Function.identity());
151        }
152        else
153        {
154            return Stream.of(ModelItemTypeHelper.compareSingleObjects(value1, value2, StringUtils.EMPTY))
155                         .filter(Optional::isPresent)
156                         .map(Optional::get);
157        }
158    }
159    
160    public boolean isSimple()
161    {
162        return true;
163    }
164    
165    @Override
166    protected boolean _useJSONForEdition()
167    {
168        return true;
169    }
170}