001/*
002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Antoine Taillefer
016 */
017package org.nuxeo.ecm.diff.service.impl;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Calendar;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.commons.collections.CollectionUtils;
030import org.apache.commons.lang.StringUtils;
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.NuxeoException;
035import org.nuxeo.ecm.core.schema.SchemaManager;
036import org.nuxeo.ecm.core.schema.types.Field;
037import org.nuxeo.ecm.core.schema.types.FieldImpl;
038import org.nuxeo.ecm.core.schema.types.QName;
039import org.nuxeo.ecm.core.schema.types.Type;
040import org.nuxeo.ecm.diff.content.ContentDiffHelper;
041import org.nuxeo.ecm.diff.model.DiffBlockDefinition;
042import org.nuxeo.ecm.diff.model.DiffComplexFieldDefinition;
043import org.nuxeo.ecm.diff.model.DiffDisplayBlock;
044import org.nuxeo.ecm.diff.model.DiffFieldDefinition;
045import org.nuxeo.ecm.diff.model.DiffFieldItemDefinition;
046import org.nuxeo.ecm.diff.model.DifferenceType;
047import org.nuxeo.ecm.diff.model.DocumentDiff;
048import org.nuxeo.ecm.diff.model.PropertyDiff;
049import org.nuxeo.ecm.diff.model.PropertyDiffDisplay;
050import org.nuxeo.ecm.diff.model.PropertyType;
051import org.nuxeo.ecm.diff.model.SchemaDiff;
052import org.nuxeo.ecm.diff.model.impl.ComplexPropertyDiff;
053import org.nuxeo.ecm.diff.model.impl.ContentDiffDisplayImpl;
054import org.nuxeo.ecm.diff.model.impl.ContentProperty;
055import org.nuxeo.ecm.diff.model.impl.ContentPropertyDiff;
056import org.nuxeo.ecm.diff.model.impl.DiffBlockDefinitionImpl;
057import org.nuxeo.ecm.diff.model.impl.DiffDisplayBlockImpl;
058import org.nuxeo.ecm.diff.model.impl.DiffFieldDefinitionImpl;
059import org.nuxeo.ecm.diff.model.impl.DiffFieldItemDefinitionImpl;
060import org.nuxeo.ecm.diff.model.impl.ListPropertyDiff;
061import org.nuxeo.ecm.diff.model.impl.PropertyDiffDisplayImpl;
062import org.nuxeo.ecm.diff.model.impl.SimplePropertyDiff;
063import org.nuxeo.ecm.diff.service.ComplexPropertyHelper;
064import org.nuxeo.ecm.diff.service.DiffDisplayService;
065import org.nuxeo.ecm.platform.forms.layout.api.BuiltinModes;
066import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
067import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition;
068import org.nuxeo.ecm.platform.forms.layout.api.LayoutRowDefinition;
069import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
070import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
071import org.nuxeo.ecm.platform.forms.layout.api.impl.FieldDefinitionImpl;
072import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutDefinitionImpl;
073import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutRowDefinitionImpl;
074import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
075import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetReferenceImpl;
076import org.nuxeo.ecm.platform.forms.layout.api.service.LayoutStore;
077import org.nuxeo.runtime.api.Framework;
078import org.nuxeo.runtime.model.ComponentInstance;
079import org.nuxeo.runtime.model.DefaultComponent;
080
081/**
082 * Default implementation of the {@link DiffDisplayService}.
083 *
084 * @author Antoine Taillefer (ataillefer@nuxeo.com)
085 * @since 5.6
086 */
087public class DiffDisplayServiceImpl extends DefaultComponent implements DiffDisplayService {
088
089    private static final long serialVersionUID = 6608445970773402827L;
090
091    private static final Log LOGGER = LogFactory.getLog(DiffDisplayServiceImpl.class);
092
093    protected static final String DIFF_DISPLAY_EXTENSION_POINT = "diffDisplay";
094
095    protected static final String DIFF_DEFAULT_DISPLAY_EXTENSION_POINT = "diffDefaultDisplay";
096
097    protected static final String DIFF_BLOCK_EXTENSION_POINT = "diffBlock";
098
099    protected static final String DIFF_WIDGET_CATEGORY = "diff";
100
101    protected static final String DIFF_BLOCK_DEFAULT_TEMPLATE_PATH = "/layouts/layout_diff_template.xhtml";
102
103    protected static final String DIFF_BLOCK_LABEL_PROPERTY_NAME = "label";
104
105    protected static final String DIFF_BLOCK_DEFAULT_LABEL_PREFIX = "label.diffBlock.";
106
107    protected static final String DIFF_WIDGET_LABEL_PREFIX = "label.";
108
109    protected static final String CONTENT_DIFF_LINKS_WIDGET_NAME = "contentDiffLinks";
110
111    protected static final String CONTENT_DIFF_LINKS_WIDGET_NAME_SUFFIX = "_contentDiffLinks";
112
113    protected static final String DIFF_WIDGET_FIELD_DEFINITION_VALUE = "value";
114
115    protected static final String DIFF_WIDGET_FIELD_DEFINITION_DIFFERENCE_TYPE = "differenceType";
116
117    protected static final String DIFF_WIDGET_FIELD_DEFINITION_STYLE_CLASS = "styleClass";
118
119    protected static final String DIFF_WIDGET_FIELD_DEFINITION_FILENAME = "filename";
120
121    protected static final String DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_HTML_CONVERSION = "displayHtmlConversion";
122
123    protected static final String DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_TEXT_CONVERSION = "displayTextConversion";
124
125    protected static final String DIFF_WIDGET_PROPERTY_DISPLAY_ALL_ITEMS = "displayAllItems";
126
127    protected static final String DIFF_WIDGET_PROPERTY_DISPLAY_ITEM_INDEXES = "displayItemIndexes";
128
129    // TODO: refactor name (not related to widget)
130    protected static final String DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD = "index";
131
132    protected static final String DIFF_LIST_WIDGET_INDEX_SUBWIDGET_TYPE = "int";
133
134    protected static final String DIFF_LIST_WIDGET_INDEX_SUBWIDGET_LABEL = "label.list.index";
135
136    protected static final String DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD = "value";
137
138    /** Diff excluded fields contributions. */
139    protected Map<String, List<String>> diffExcludedFieldsContribs = new HashMap<String, List<String>>();
140
141    /** Diff complex fields contributions. */
142    protected Map<String, Map<String, DiffComplexFieldDefinition>> diffComplexFieldsContribs = new HashMap<String, Map<String, DiffComplexFieldDefinition>>();
143
144    /** Diff display contributions. */
145    protected Map<String, List<String>> diffDisplayContribs = new HashMap<String, List<String>>();
146
147    /** Diff block contributions. */
148    protected Map<String, DiffBlockDefinition> diffBlockContribs = new HashMap<String, DiffBlockDefinition>();
149
150    @Override
151    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
152
153        if (DIFF_DEFAULT_DISPLAY_EXTENSION_POINT.equals(extensionPoint)) {
154            if (contribution instanceof DiffExcludedFieldsDescriptor) {
155                registerDiffExcludedFields((DiffExcludedFieldsDescriptor) contribution);
156            } else if (contribution instanceof DiffComplexFieldDescriptor) {
157                registerDiffComplexField((DiffComplexFieldDescriptor) contribution);
158            }
159        } else if (DIFF_DISPLAY_EXTENSION_POINT.equals(extensionPoint)) {
160            if (contribution instanceof DiffDisplayDescriptor) {
161                registerDiffDisplay((DiffDisplayDescriptor) contribution);
162            }
163        } else if (DIFF_BLOCK_EXTENSION_POINT.equals(extensionPoint)) {
164            if (contribution instanceof DiffBlockDescriptor) {
165                registerDiffBlock((DiffBlockDescriptor) contribution);
166            }
167        }
168        super.registerContribution(contribution, extensionPoint, contributor);
169    }
170
171    @Override
172    public Map<String, List<String>> getDiffExcludedSchemas() {
173        return diffExcludedFieldsContribs;
174    }
175
176    @Override
177    public List<String> getDiffExcludedFields(String schemaName) {
178        return diffExcludedFieldsContribs.get(schemaName);
179    }
180
181    @Override
182    public List<DiffComplexFieldDefinition> getDiffComplexFields() {
183        List<DiffComplexFieldDefinition> diffComplexFields = new ArrayList<DiffComplexFieldDefinition>();
184        for (Map<String, DiffComplexFieldDefinition> diffComplexFieldsBySchema : diffComplexFieldsContribs.values()) {
185            for (DiffComplexFieldDefinition diffComplexField : diffComplexFieldsBySchema.values()) {
186                diffComplexFields.add(diffComplexField);
187            }
188        }
189        return diffComplexFields;
190    }
191
192    @Override
193    public DiffComplexFieldDefinition getDiffComplexField(String schemaName, String fieldName) {
194        Map<String, DiffComplexFieldDefinition> diffComplexFieldsBySchema = diffComplexFieldsContribs.get(schemaName);
195        if (diffComplexFieldsBySchema != null) {
196            return diffComplexFieldsBySchema.get(fieldName);
197        }
198        return null;
199    }
200
201    @Override
202    public Map<String, List<String>> getDiffDisplays() {
203        return diffDisplayContribs;
204    }
205
206    @Override
207    public List<String> getDiffDisplay(String docType) {
208        return diffDisplayContribs.get(docType);
209
210    }
211
212    @Override
213    public Map<String, DiffBlockDefinition> getDiffBlockDefinitions() {
214        return diffBlockContribs;
215    }
216
217    @Override
218    public DiffBlockDefinition getDiffBlockDefinition(String name) {
219        return diffBlockContribs.get(name);
220    }
221
222    @Override
223    public List<DiffDisplayBlock> getDiffDisplayBlocks(DocumentDiff docDiff, DocumentModel leftDoc,
224            DocumentModel rightDoc) {
225
226        String leftDocType = leftDoc.getType();
227        String rightDocType = rightDoc.getType();
228        if (leftDocType.equals(rightDocType)) {
229            LOGGER.info(String.format(
230                    "The 2 documents have the same type '%s' => looking for a diffDisplay contribution defined for this type or the nearest super type.",
231                    leftDocType));
232            List<String> diffBlockRefs = getNearestSuperTypeDiffDisplay(leftDocType);
233            if (diffBlockRefs != null) {
234                LOGGER.info(String.format(
235                        "Found a diffDisplay contribution defined for the type '%s' or one of its super type => using it to display the diff.",
236                        leftDocType));
237                return getDiffDisplayBlocks(getDiffBlockDefinitions(diffBlockRefs), docDiff, leftDoc, rightDoc);
238            } else {
239                LOGGER.info(String.format(
240                        "No diffDisplay contribution was defined for the type '%s' or one of its super type => using default diff display.",
241                        leftDocType));
242            }
243        } else {
244            LOGGER.info(String.format(
245                    "The 2 documents don't have the same type: '%s'/'%s' => looking for a diffDisplay contribution defined for the nearest common super type.",
246                    leftDocType, rightDocType));
247            List<String> diffBlockRefs = getNearestSuperTypeDiffDisplay(leftDocType, rightDocType);
248            if (diffBlockRefs != null) {
249                LOGGER.info(String.format(
250                        "Found a diffDisplay contribution defined for a common super type of the types '%s'/'%s' => using it to display the diff.",
251                        leftDocType, rightDocType));
252                return getDiffDisplayBlocks(getDiffBlockDefinitions(diffBlockRefs), docDiff, leftDoc, rightDoc);
253            } else {
254                LOGGER.info(String.format(
255                        "No diffDisplay contribution was defined for any of the common super types of the types '%s'/'%s' => using default diff display.",
256                        leftDocType, rightDocType));
257            }
258        }
259        return getDefaultDiffDisplayBlocks(docDiff, leftDoc, rightDoc);
260    }
261
262    public List<DiffDisplayBlock> getDefaultDiffDisplayBlocks(DocumentDiff docDiff, DocumentModel leftDoc,
263            DocumentModel rightDoc) {
264
265        return getDiffDisplayBlocks(getDefaultDiffBlockDefinitions(docDiff), docDiff, leftDoc, rightDoc);
266    }
267
268    protected List<String> getNearestSuperTypeDiffDisplay(String docTypeName) {
269
270        List<String> diffDisplay = getDiffDisplay(docTypeName);
271        Type docType = getSchemaManager().getDocumentType(docTypeName);
272        while (diffDisplay == null && docType != null) {
273            Type superType = docType.getSuperType();
274            if (superType != null) {
275                diffDisplay = getDiffDisplay(superType.getName());
276            }
277            docType = superType;
278        }
279        return diffDisplay;
280    }
281
282    protected List<String> getNearestSuperTypeDiffDisplay(String leftDocTypeName, String rightDocTypeName)
283            {
284
285        List<String> diffDisplay = null;
286
287        List<String> leftDocSuperTypeNames = new ArrayList<String>();
288        List<String> rightDocSuperTypeNames = new ArrayList<String>();
289        leftDocSuperTypeNames.add(leftDocTypeName);
290        rightDocSuperTypeNames.add(rightDocTypeName);
291
292        Type leftDocType = getSchemaManager().getDocumentType(leftDocTypeName);
293        Type rightDocType = getSchemaManager().getDocumentType(rightDocTypeName);
294        if (leftDocType != null && rightDocType != null) {
295            Type[] leftDocTypeHierarchy = leftDocType.getTypeHierarchy();
296            for (Type type : leftDocTypeHierarchy) {
297                leftDocSuperTypeNames.add(type.getName());
298            }
299            Type[] rightDocTypeHierarchy = rightDocType.getTypeHierarchy();
300            for (Type type : rightDocTypeHierarchy) {
301                rightDocSuperTypeNames.add(type.getName());
302            }
303        }
304
305        for (String superTypeName : leftDocSuperTypeNames) {
306            if (rightDocSuperTypeNames.contains(superTypeName)) {
307                return getNearestSuperTypeDiffDisplay(superTypeName);
308            }
309        }
310
311        return diffDisplay;
312    }
313
314    /**
315     * Registers a diff excluded fields contrib.
316     */
317    protected final void registerDiffExcludedFields(DiffExcludedFieldsDescriptor descriptor) {
318
319        String schemaName = descriptor.getSchema();
320        if (!StringUtils.isEmpty(schemaName)) {
321            boolean enabled = descriptor.isEnabled();
322            // Check existing diffExcludedFields contrib for this schema
323            List<String> diffExcludedFields = diffExcludedFieldsContribs.get(schemaName);
324            if (diffExcludedFields != null) {
325                // If !enabled remove contrib
326                if (!enabled) {
327                    diffExcludedFieldsContribs.remove(schemaName);
328                }
329                // Else override contrib (no merge)
330                // TODO: implement merge
331                else {
332                    diffExcludedFieldsContribs.put(schemaName, getDiffExcludedFieldRefs(descriptor.getFields()));
333                }
334            }
335            // No existing diffExcludedFields contrib for this
336            // schema and enabled => add contrib
337            else if (enabled) {
338                diffExcludedFieldsContribs.put(schemaName, getDiffExcludedFieldRefs(descriptor.getFields()));
339            }
340        }
341    }
342
343    /**
344     * Registers a diff complex field contrib.
345     */
346    protected final void registerDiffComplexField(DiffComplexFieldDescriptor descriptor) {
347
348        String schemaName = descriptor.getSchema();
349        String fieldName = descriptor.getName();
350        if (!StringUtils.isEmpty(schemaName) && !StringUtils.isEmpty(fieldName)) {
351            // Check existing diffComplexField contrib for this schema/field
352            DiffComplexFieldDefinition diffComplexField = getDiffComplexField(schemaName, fieldName);
353            if (diffComplexField != null) {
354                // Override contrib (no merge)
355                // TODO: implement merge
356                diffComplexFieldsContribs.get(schemaName).put(fieldName, descriptor.getDiffComplexFieldDefinition());
357            }
358            // No existing diffComplexField contrib for this
359            // schema/field => add contrib
360            else {
361                Map<String, DiffComplexFieldDefinition> diffComplexFieldsBySchema = diffComplexFieldsContribs.get(schemaName);
362                if (diffComplexFieldsBySchema == null) {
363                    diffComplexFieldsBySchema = new HashMap<String, DiffComplexFieldDefinition>();
364                    diffComplexFieldsContribs.put(schemaName, diffComplexFieldsBySchema);
365                }
366                diffComplexFieldsBySchema.put(fieldName, descriptor.getDiffComplexFieldDefinition());
367            }
368        }
369    }
370
371    /**
372     * Registers a diff display contrib.
373     *
374     * @param contribution the contribution
375     */
376    protected final void registerDiffDisplay(DiffDisplayDescriptor descriptor) {
377
378        String docType = descriptor.getType();
379        if (!StringUtils.isEmpty(docType)) {
380            boolean enabled = descriptor.isEnabled();
381            // Check existing diffDisplay contrib for this type
382            List<String> diffDisplay = diffDisplayContribs.get(docType);
383            if (diffDisplay != null) {
384                // If !enabled remove contrib
385                if (!enabled) {
386                    diffDisplayContribs.remove(docType);
387                }
388                // Else override contrib (no merge)
389                // TODO: implement merge
390                else {
391                    diffDisplayContribs.put(docType, getDiffBlockRefs(descriptor.getDiffBlocks()));
392                }
393            }
394            // No existing diffDisplay contrib for this
395            // type and enabled => add contrib
396            else if (enabled) {
397                diffDisplayContribs.put(docType, getDiffBlockRefs(descriptor.getDiffBlocks()));
398            }
399        }
400    }
401
402    protected final List<String> getDiffBlockRefs(List<DiffBlockReferenceDescriptor> diffBlocks) {
403
404        List<String> diffBlockRefs = new ArrayList<String>();
405        for (DiffBlockReferenceDescriptor diffBlockRef : diffBlocks) {
406            diffBlockRefs.add(diffBlockRef.getName());
407        }
408        return diffBlockRefs;
409    }
410
411    protected final List<String> getDiffExcludedFieldRefs(List<DiffFieldDescriptor> diffExcludedFields) {
412
413        List<String> diffExcludedFieldRefs = new ArrayList<String>();
414        for (DiffFieldDescriptor diffExcludedFieldRef : diffExcludedFields) {
415            diffExcludedFieldRefs.add(diffExcludedFieldRef.getName());
416        }
417        return diffExcludedFieldRefs;
418    }
419
420    protected final void registerDiffBlock(DiffBlockDescriptor descriptor) {
421
422        String diffBlockName = descriptor.getName();
423        if (!StringUtils.isEmpty(diffBlockName)) {
424            List<DiffFieldDescriptor> fieldDescriptors = descriptor.getFields();
425            // No field descriptors => don't take diff block into account.
426            if (fieldDescriptors == null || fieldDescriptors.isEmpty()) {
427                LOGGER.warn(String.format(
428                        "The diffBlock contribution named '%s' has no fields, it won't be taken into account.",
429                        diffBlockName));
430            } else {
431                List<DiffFieldDefinition> fields = new ArrayList<DiffFieldDefinition>();
432                // Some field descriptors were found => use them to add the
433                // described fields, taking their order into account.
434                for (DiffFieldDescriptor fieldDescriptor : fieldDescriptors) {
435                    String category = fieldDescriptor.getCategory();
436                    String schema = fieldDescriptor.getSchema();
437                    String name = fieldDescriptor.getName();
438                    boolean displayContentDiffLinks = fieldDescriptor.isDisplayContentDiffLinks();
439                    List<DiffFieldItemDescriptor> fieldItemDescriptors = fieldDescriptor.getItems();
440                    if (!StringUtils.isEmpty(schema) && !StringUtils.isEmpty(name)) {
441                        List<DiffFieldItemDefinition> items = new ArrayList<DiffFieldItemDefinition>();
442                        for (DiffFieldItemDescriptor fieldItemDescriptor : fieldItemDescriptors) {
443                            items.add(new DiffFieldItemDefinitionImpl(fieldItemDescriptor.getName(),
444                                    fieldItemDescriptor.isDisplayContentDiffLinks()));
445                        }
446                        fields.add(new DiffFieldDefinitionImpl(category, schema, name, displayContentDiffLinks, items));
447                    }
448                }
449                // TODO: implement merge
450                diffBlockContribs.put(
451                        diffBlockName,
452                        new DiffBlockDefinitionImpl(diffBlockName, descriptor.getTemplates(), fields,
453                                descriptor.getProperties()));
454            }
455        }
456    }
457
458    protected final List<DiffBlockDefinition> getDefaultDiffBlockDefinitions(DocumentDiff docDiff)
459            {
460
461        List<DiffBlockDefinition> diffBlockDefs = new ArrayList<DiffBlockDefinition>();
462
463        for (String schemaName : docDiff.getSchemaNames()) {
464            List<String> diffExcludedFields = getDiffExcludedFields(schemaName);
465            // Only add the schema fields if the whole schema is not excluded
466            if (diffExcludedFields == null || diffExcludedFields.size() > 0) {
467                SchemaDiff schemaDiff = docDiff.getSchemaDiff(schemaName);
468                List<DiffFieldDefinition> fieldDefs = new ArrayList<DiffFieldDefinition>();
469                for (String fieldName : schemaDiff.getFieldNames()) {
470                    // Only add the field if it is not excluded
471                    if (diffExcludedFields == null || !diffExcludedFields.contains(fieldName)) {
472                        List<DiffFieldItemDefinition> fieldItems = new ArrayList<DiffFieldItemDefinition>();
473                        DiffComplexFieldDefinition complexFieldDef = getDiffComplexField(schemaName, fieldName);
474                        if (complexFieldDef != null) {
475                            List<DiffFieldItemDefinition> includedItems = complexFieldDef.getIncludedItems();
476                            List<DiffFieldItemDefinition> excludedItems = complexFieldDef.getExcludedItems();
477                            // Check included field items
478                            if (!CollectionUtils.isEmpty(includedItems)) {
479                                fieldItems.addAll(includedItems);
480                            }
481                            // Check excluded field items
482                            else if (!CollectionUtils.isEmpty(excludedItems)) {
483                                Field complexField = ComplexPropertyHelper.getField(schemaName, fieldName);
484                                if (complexField.getType().isListType()) {
485                                    complexField = ComplexPropertyHelper.getListFieldItem(complexField);
486                                }
487                                if (!complexField.getType().isComplexType()) {
488                                    throw new NuxeoException(
489                                            String.format(
490                                                    "Cannot compute field items for [%s:%s] since it is not a complex nor a complex list property.",
491                                                    schemaName, fieldName));
492                                }
493                                List<Field> complexFieldItems = ComplexPropertyHelper.getComplexFieldItems(complexField);
494                                for (Field complexFieldItem : complexFieldItems) {
495                                    String complexFieldItemName = complexFieldItem.getName().getLocalName();
496                                    boolean isFieldItem = true;
497                                    for (DiffFieldItemDefinition fieldItemDef : excludedItems) {
498                                        if (fieldItemDef.getName().equals(complexFieldItemName)) {
499                                            isFieldItem = false;
500                                            break;
501                                        }
502                                    }
503                                    if (isFieldItem) {
504                                        fieldItems.add(new DiffFieldItemDefinitionImpl(complexFieldItemName));
505                                    }
506                                }
507                            }
508                        }
509                        fieldDefs.add(new DiffFieldDefinitionImpl(DIFF_WIDGET_CATEGORY, schemaName, fieldName,
510                                fieldItems));
511                    }
512                }
513
514                Map<String, String> defaultDiffBlockTemplates = new HashMap<String, String>();
515                defaultDiffBlockTemplates.put(BuiltinModes.ANY, DIFF_BLOCK_DEFAULT_TEMPLATE_PATH);
516
517                Map<String, Map<String, Serializable>> defaultDiffBlockProperties = new HashMap<String, Map<String, Serializable>>();
518                Map<String, Serializable> labelProperty = new HashMap<String, Serializable>();
519                labelProperty.put(DIFF_BLOCK_LABEL_PROPERTY_NAME, DIFF_BLOCK_DEFAULT_LABEL_PREFIX + schemaName);
520                defaultDiffBlockProperties.put(BuiltinModes.ANY, labelProperty);
521
522                diffBlockDefs.add(new DiffBlockDefinitionImpl(schemaName, defaultDiffBlockTemplates, fieldDefs,
523                        defaultDiffBlockProperties));
524            }
525        }
526
527        return diffBlockDefs;
528    }
529
530    protected final List<DiffBlockDefinition> getDiffBlockDefinitions(List<String> diffBlockRefs) {
531
532        List<DiffBlockDefinition> diffBlockDefinitions = new ArrayList<DiffBlockDefinition>();
533        for (String diffBlockRef : diffBlockRefs) {
534            diffBlockDefinitions.add(getDiffBlockDefinition(diffBlockRef));
535        }
536        return diffBlockDefinitions;
537    }
538
539    protected final List<DiffDisplayBlock> getDiffDisplayBlocks(List<DiffBlockDefinition> diffBlockDefinitions,
540            DocumentDiff docDiff, DocumentModel leftDoc, DocumentModel rightDoc) {
541
542        List<DiffDisplayBlock> diffDisplayBlocks = new ArrayList<DiffDisplayBlock>();
543
544        for (DiffBlockDefinition diffBlockDef : diffBlockDefinitions) {
545            if (diffBlockDef != null) {
546                DiffDisplayBlock diffDisplayBlock = getDiffDisplayBlock(diffBlockDef, docDiff, leftDoc, rightDoc);
547                if (!diffDisplayBlock.isEmpty()) {
548                    diffDisplayBlocks.add(diffDisplayBlock);
549                }
550            }
551        }
552
553        return diffDisplayBlocks;
554    }
555
556    protected final DiffDisplayBlock getDiffDisplayBlock(DiffBlockDefinition diffBlockDefinition, DocumentDiff docDiff,
557            DocumentModel leftDoc, DocumentModel rightDoc) {
558
559        Map<String, Map<String, PropertyDiffDisplay>> leftValue = new HashMap<String, Map<String, PropertyDiffDisplay>>();
560        Map<String, Map<String, PropertyDiffDisplay>> rightValue = new HashMap<String, Map<String, PropertyDiffDisplay>>();
561        Map<String, Map<String, PropertyDiffDisplay>> contentDiffValue = new HashMap<String, Map<String, PropertyDiffDisplay>>();
562
563        List<LayoutRowDefinition> layoutRowDefinitions = new ArrayList<LayoutRowDefinition>();
564        List<WidgetDefinition> widgetDefinitions = new ArrayList<WidgetDefinition>();
565
566        List<DiffFieldDefinition> fieldDefinitions = diffBlockDefinition.getFields();
567        for (DiffFieldDefinition fieldDefinition : fieldDefinitions) {
568
569            String category = fieldDefinition.getCategory();
570            if (StringUtils.isEmpty(category)) {
571                category = DIFF_WIDGET_CATEGORY;
572            }
573            String schemaName = fieldDefinition.getSchema();
574            String fieldName = fieldDefinition.getName();
575            boolean displayContentDiffLinks = fieldDefinition.isDisplayContentDiffLinks();
576            List<DiffFieldItemDefinition> fieldItemDefs = fieldDefinition.getItems();
577
578            SchemaDiff schemaDiff = docDiff.getSchemaDiff(schemaName);
579            if (schemaDiff != null) {
580                PropertyDiff fieldDiff = schemaDiff.getFieldDiff(fieldName);
581                if (fieldDiff != null) {
582
583                    Serializable leftProperty = (Serializable) leftDoc.getProperty(schemaName, fieldName);
584                    Serializable rightProperty = (Serializable) rightDoc.getProperty(schemaName, fieldName);
585
586                    // Only include field diff if it is significant
587                    if (isFieldDiffSignificant(leftProperty, rightProperty)) {
588
589                        String propertyName = getPropertyName(schemaName, fieldName);
590                        List<WidgetReference> widgetReferences = new ArrayList<WidgetReference>();
591
592                        // Set property widget definition
593                        WidgetDefinition propertyWidgetDefinition = getWidgetDefinition(category, propertyName,
594                                fieldDiff.getPropertyType(), null, fieldItemDefs, false);
595                        widgetDefinitions.add(propertyWidgetDefinition);
596                        // Set property widget ref
597                        WidgetReferenceImpl propertyWidgetRef = new WidgetReferenceImpl(category, propertyName);
598                        widgetReferences.add(propertyWidgetRef);
599
600                        // Check if must display the content diff links widget
601                        if (!displayContentDiffLinks) {
602                            for (DiffFieldItemDefinition fieldItemDef : fieldItemDefs) {
603                                if (fieldItemDef.isDisplayContentDiffLinks()) {
604                                    displayContentDiffLinks = true;
605                                    break;
606                                }
607                            }
608                        }
609                        // Set content diff links widget definition and ref if
610                        // needed
611                        if (displayContentDiffLinks) {
612                            WidgetDefinition contentDiffLinksWidgetDefinition = getWidgetDefinition(category,
613                                    propertyName, fieldDiff.getPropertyType(), null, fieldItemDefs, true);
614                            widgetDefinitions.add(contentDiffLinksWidgetDefinition);
615                            WidgetReferenceImpl contentDiffLinksWidgetRef = new WidgetReferenceImpl(category,
616                                    propertyName + CONTENT_DIFF_LINKS_WIDGET_NAME_SUFFIX);
617                            widgetReferences.add(contentDiffLinksWidgetRef);
618                        }
619
620                        // Set layout row definition
621                        LayoutRowDefinition layoutRowDefinition = new LayoutRowDefinitionImpl(propertyName, null,
622                                widgetReferences, false, true);
623                        layoutRowDefinitions.add(layoutRowDefinition);
624
625                        // Set diff display field value
626                        boolean isDisplayAllItems = isDisplayAllItems(propertyWidgetDefinition);
627                        boolean isDisplayItemIndexes = isDisplayItemIndexes(propertyWidgetDefinition);
628
629                        // Left diff display
630                        setFieldDiffDisplay(leftProperty, fieldDiff, isDisplayAllItems, isDisplayItemIndexes,
631                                leftValue, schemaName, fieldName, leftDoc,
632                                PropertyDiffDisplay.RED_BACKGROUND_STYLE_CLASS);
633
634                        // Right diff display
635                        setFieldDiffDisplay(rightProperty, fieldDiff, isDisplayAllItems, isDisplayItemIndexes,
636                                rightValue, schemaName, fieldName, rightDoc,
637                                PropertyDiffDisplay.GREEN_BACKGROUND_STYLE_CLASS);
638
639                        // Content diff display
640                        if (displayContentDiffLinks) {
641                            PropertyDiffDisplay contentDiffDisplay = getFieldXPaths(propertyName, fieldDiff,
642                                    leftProperty, rightProperty, isDisplayAllItems, isDisplayItemIndexes, fieldItemDefs);
643                            Map<String, PropertyDiffDisplay> contentDiffSchemaMap = contentDiffValue.get(schemaName);
644                            if (contentDiffSchemaMap == null) {
645                                contentDiffSchemaMap = new HashMap<String, PropertyDiffDisplay>();
646                                contentDiffValue.put(schemaName, contentDiffSchemaMap);
647                            }
648                            contentDiffSchemaMap.put(fieldName, contentDiffDisplay);
649                        }
650                    }
651                }
652            }
653        }
654
655        // Build layout definition
656        LayoutDefinition layoutDefinition = new LayoutDefinitionImpl(diffBlockDefinition.getName(),
657                diffBlockDefinition.getProperties(), diffBlockDefinition.getTemplates(), layoutRowDefinitions,
658                widgetDefinitions);
659
660        // Build diff display block
661        Map<String, Serializable> diffBlockProperties = diffBlockDefinition.getProperties(BuiltinModes.ANY);
662        DiffDisplayBlock diffDisplayBlock = new DiffDisplayBlockImpl(
663                (String) diffBlockProperties.get(DIFF_BLOCK_LABEL_PROPERTY_NAME), leftValue, rightValue,
664                contentDiffValue, layoutDefinition);
665
666        return diffDisplayBlock;
667    }
668
669    /**
670     * Checks if the difference between the two specified properties is significant.
671     * <p>
672     * For example in the case of a date property, checks if the difference is greater than 1 minute since we don't
673     * display seconds in the default date widget.
674     */
675    protected boolean isFieldDiffSignificant(Serializable leftProperty, Serializable rightProperty) {
676
677        if (leftProperty instanceof Calendar && rightProperty instanceof Calendar) {
678            Calendar leftDate = (Calendar) leftProperty;
679            Calendar rightDate = (Calendar) rightProperty;
680            if (Math.abs(leftDate.getTimeInMillis() - rightDate.getTimeInMillis()) <= 60000) {
681                return false;
682            }
683        }
684        return true;
685    }
686
687    protected final boolean isDisplayAllItems(WidgetDefinition wDef) {
688
689        // Check 'displayAllItems' widget property
690        return getBooleanProperty(wDef, BuiltinModes.ANY, DIFF_WIDGET_PROPERTY_DISPLAY_ALL_ITEMS);
691    }
692
693    protected final boolean isDisplayItemIndexes(WidgetDefinition wDef) {
694
695        // Check 'displayItemIndexes' widget property
696        return getBooleanProperty(wDef, BuiltinModes.ANY, DIFF_WIDGET_PROPERTY_DISPLAY_ITEM_INDEXES);
697    }
698
699    protected final boolean getBooleanProperty(WidgetDefinition wDef, String mode, String property) {
700
701        Map<String, Map<String, Serializable>> props = wDef.getProperties();
702        if (props != null) {
703            Map<String, Serializable> modeProps = props.get(mode);
704            if (modeProps != null) {
705                Serializable propertyValue = modeProps.get(property);
706                if (propertyValue instanceof String) {
707                    return Boolean.parseBoolean((String) propertyValue);
708                }
709            }
710        }
711        return false;
712    }
713
714    /**
715     * Sets the field diff display.
716     *
717     * @param property the property
718     * @param fieldDiff the field diff
719     * @param isDisplayAllItems the is display all items
720     * @param isDisplayItemIndexes the is display item indexes
721     * @param value the value
722     * @param schemaName the schema name
723     * @param fieldName the field name
724     * @param doc the doc
725     * @param styleClass the style class
726     */
727    protected void setFieldDiffDisplay(Serializable property, PropertyDiff fieldDiff, boolean isDisplayAllItems,
728            boolean isDisplayItemIndexes, Map<String, Map<String, PropertyDiffDisplay>> value, String schemaName,
729            String fieldName, DocumentModel doc, String styleClass) {
730
731        PropertyDiffDisplay fieldDiffDisplay = getFieldDiffDisplay(property, fieldDiff, isDisplayAllItems,
732                isDisplayItemIndexes, false, styleClass);
733        Map<String, PropertyDiffDisplay> schemaMap = value.get(schemaName);
734        if (schemaMap == null) {
735            schemaMap = new HashMap<String, PropertyDiffDisplay>();
736            value.put(schemaName, schemaMap);
737        }
738        schemaMap.put(fieldName, fieldDiffDisplay);
739        // Handle mime type for the note:note property
740        putMimeTypeDiffDisplay(schemaName, fieldName, schemaMap, doc);
741    }
742
743    protected final void putMimeTypeDiffDisplay(String schemaName, String fieldName,
744            Map<String, PropertyDiffDisplay> schemaMap, DocumentModel doc) {
745
746        if ("note".equals(schemaName) && "note".equals(fieldName) && !schemaMap.containsKey("mime_type")) {
747            schemaMap.put("mime_type", new PropertyDiffDisplayImpl((Serializable) doc.getProperty("note", "mime_type")));
748        }
749    }
750
751    protected final PropertyDiffDisplay getFieldDiffDisplay(Serializable property, PropertyDiff propertyDiff,
752            boolean isDisplayAllItems, boolean isDisplayItemIndexes, boolean mustApplyStyleClass, String styleClass)
753            {
754
755        if (property == null) {
756            String fieldDiffDisplayStyleClass = PropertyDiffDisplay.DEFAULT_STYLE_CLASS;
757            if (mustApplyStyleClass && propertyDiff != null) {
758                fieldDiffDisplayStyleClass = styleClass;
759            }
760            // TODO: add DifferenceType.removed?
761            return new PropertyDiffDisplayImpl(null, fieldDiffDisplayStyleClass);
762        }
763
764        // List type
765        if (isListType(property)) {
766            List<Serializable> listProperty = getListProperty(property);
767            return getListFieldDiffDisplay(listProperty, (ListPropertyDiff) propertyDiff, isDisplayAllItems,
768                    isDisplayItemIndexes, styleClass);
769        }
770        // Other types (scalar, complex, content)
771        else {
772            return getFinalFieldDiffDisplay(property, propertyDiff, mustApplyStyleClass, styleClass);
773        }
774    }
775
776    protected final PropertyDiffDisplay getFinalFieldDiffDisplay(Serializable fieldDiffDisplay,
777            PropertyDiff propertyDiff, boolean mustApplyStyleClass, String styleClass) {
778
779        String finalFieldDiffDisplayStyleClass = PropertyDiffDisplay.DEFAULT_STYLE_CLASS;
780        if (mustApplyStyleClass && propertyDiff != null) {
781            finalFieldDiffDisplayStyleClass = styleClass;
782        }
783        PropertyDiffDisplay finalFieldDiffDisplay;
784        if (isComplexType(fieldDiffDisplay)) {
785            ComplexPropertyDiff complexPropertyDiff = null;
786            if (propertyDiff != null) {
787                if (!propertyDiff.isComplexType()) {
788                    throw new NuxeoException(
789                            "'fieldDiffDisplay' is of complex type whereas 'propertyDiff' is not, this is inconsistent");
790                }
791                complexPropertyDiff = (ComplexPropertyDiff) propertyDiff;
792            }
793            Map<String, Serializable> complexFieldDiffDisplay = getComplexProperty(fieldDiffDisplay);
794            for (String complexItemName : complexFieldDiffDisplay.keySet()) {
795                PropertyDiff complexItemPropertyDiff = null;
796                if (complexPropertyDiff != null) {
797                    complexItemPropertyDiff = complexPropertyDiff.getDiff(complexItemName);
798                }
799                complexFieldDiffDisplay.put(complexItemName,
800                // TODO: shouldn't we call getFieldDiffDisplay in case
801                // of an embedded list?
802                        getFinalFieldDiffDisplay(complexFieldDiffDisplay.get(complexItemName), complexItemPropertyDiff,
803                                true, styleClass));
804            }
805            finalFieldDiffDisplay = new PropertyDiffDisplayImpl((Serializable) complexFieldDiffDisplay);
806        } else if (fieldDiffDisplay instanceof Calendar) {
807            // TODO: add propertyDiff.getDifferenceType()?
808            finalFieldDiffDisplay = new PropertyDiffDisplayImpl(((Calendar) fieldDiffDisplay).getTime(),
809                    finalFieldDiffDisplayStyleClass);
810        } else {
811            // TODO: add propertyDiff.getDifferenceType()?
812            finalFieldDiffDisplay = new PropertyDiffDisplayImpl(fieldDiffDisplay, finalFieldDiffDisplayStyleClass);
813        }
814        return finalFieldDiffDisplay;
815    }
816
817    /**
818     * Gets the list field diff display.
819     *
820     * @param listProperty the list property
821     * @param listPropertyDiff the list property diff
822     * @param isDisplayAllItems the is display all items
823     * @param isDisplayItemIndexes the is display item indexes
824     * @param styleClass the style class
825     * @return the list field diff display
826     */
827    protected final PropertyDiffDisplay getListFieldDiffDisplay(List<Serializable> listProperty,
828            ListPropertyDiff listPropertyDiff, boolean isDisplayAllItems, boolean isDisplayItemIndexes,
829            String styleClass) {
830
831        // Get list property indexes
832        // By default: only items that are different (ie. held by the
833        // propertyDiff)
834        List<Integer> listPropertyIndexes = new ArrayList<Integer>();
835        if (isDisplayAllItems) {
836            // All items
837            for (int index = 0; index < listProperty.size(); index++) {
838                listPropertyIndexes.add(index);
839            }
840        } else {
841            if (listPropertyDiff != null) {
842                listPropertyIndexes = listPropertyDiff.getDiffIndexes();
843            }
844        }
845
846        return getComplexListFieldDiffDisplay(listProperty, listPropertyIndexes, listPropertyDiff, isDisplayAllItems,
847                isDisplayItemIndexes, styleClass);
848    }
849
850    protected final PropertyDiffDisplay getComplexListFieldDiffDisplay(List<Serializable> listProperty,
851            List<Integer> listPropertyIndexes, ListPropertyDiff listPropertyDiff, boolean isDisplayAllItems,
852            boolean isDisplayItemIndexes, String styleClass) {
853
854        if (listPropertyIndexes.isEmpty()) {
855            // TODO: add differenceType?
856            return new PropertyDiffDisplayImpl(new ArrayList<Serializable>());
857        }
858        boolean isComplexListWidget = isDisplayItemIndexes
859                || (listPropertyDiff != null && listPropertyDiff.isComplexListType());
860
861        if (isComplexListWidget) {
862            List<Map<String, Serializable>> listFieldDiffDisplay = new ArrayList<Map<String, Serializable>>();
863            Set<String> complexPropertyItemNames = null;
864            for (int index : listPropertyIndexes) {
865
866                Map<String, Serializable> listItemDiffDisplay = new HashMap<String, Serializable>();
867                // Put item index if wanted
868                if (isDisplayItemIndexes) {
869                    listItemDiffDisplay.put(DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD, index + 1);
870                }
871                // Only put value if index is in list range
872                if (index < listProperty.size()) {
873                    Serializable listPropertyItem = listProperty.get(index);
874                    PropertyDiff listItemPropertyDiff = null;
875                    if (listPropertyDiff != null) {
876                        listItemPropertyDiff = listPropertyDiff.getDiff(index);
877                    }
878                    if (isComplexType(listPropertyItem)) { // Complex
879                                                           // list
880                        ComplexPropertyDiff complexPropertyDiff = null;
881                        if (listItemPropertyDiff != null && listItemPropertyDiff.isComplexType()) {
882                            complexPropertyDiff = (ComplexPropertyDiff) listItemPropertyDiff;
883                        }
884                        Map<String, Serializable> complexProperty = getComplexProperty(listPropertyItem);
885                        complexPropertyItemNames = complexProperty.keySet();
886                        for (String complexPropertyItemName : complexPropertyItemNames) {
887                            Serializable complexPropertyItem = complexProperty.get(complexPropertyItemName);
888                            // TODO: take into account subwidget properties
889                            // 'displayAllItems' and 'displayItemIndexes'
890                            // instead of inheriting them from the parent
891                            // widget.
892                            PropertyDiff complexItemPropertyDiff = null;
893                            if (complexPropertyDiff != null) {
894                                complexItemPropertyDiff = complexPropertyDiff.getDiff(complexPropertyItemName);
895                            }
896                            listItemDiffDisplay.put(
897                                    complexPropertyItemName,
898                                    getFieldDiffDisplay(complexPropertyItem, complexItemPropertyDiff,
899                                            isDisplayAllItems, isDisplayItemIndexes, true, styleClass));
900                        }
901                    } else { // Scalar or content list
902                        listItemDiffDisplay.put(
903                                DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD,
904                                getFinalFieldDiffDisplay(listPropertyItem, listItemPropertyDiff, isDisplayAllItems,
905                                        styleClass));
906                    }
907                } else {// Index not in list range => put null value
908                    if (complexPropertyItemNames != null) {
909                        for (String complexPropertyItemName : complexPropertyItemNames) {
910                            // TODO: add DifferenceType.removed?
911                            listItemDiffDisplay.put(complexPropertyItemName, new PropertyDiffDisplayImpl(null,
912                                    styleClass));
913                        }
914                    } else {
915                        // TODO: add DifferenceType.removed?
916                        listItemDiffDisplay.put(DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD, new PropertyDiffDisplayImpl(
917                                null, isDisplayAllItems ? styleClass : PropertyDiffDisplay.DEFAULT_STYLE_CLASS));
918                    }
919                }
920                listFieldDiffDisplay.add(listItemDiffDisplay);
921            }
922            return new PropertyDiffDisplayImpl((Serializable) listFieldDiffDisplay);
923        } else {
924            List<Serializable> listFieldDiffDisplay = new ArrayList<Serializable>();
925            for (int index : listPropertyIndexes) {
926                // Only put value if index is in list range
927                if (index < listProperty.size()) {
928                    PropertyDiff listItemPropertyDiff = null;
929                    if (listPropertyDiff != null) {
930                        listItemPropertyDiff = listPropertyDiff.getDiff(index);
931                    }
932                    listFieldDiffDisplay.add(getFinalFieldDiffDisplay(listProperty.get(index), listItemPropertyDiff,
933                            isDisplayAllItems, styleClass));
934                } else {// Index not in list range => put null value
935                    // TODO: add DifferenceType.removed?
936                    listFieldDiffDisplay.add(new PropertyDiffDisplayImpl(null, isDisplayAllItems ? styleClass
937                            : PropertyDiffDisplay.DEFAULT_STYLE_CLASS));
938                }
939            }
940            return new PropertyDiffDisplayImpl((Serializable) listFieldDiffDisplay);
941        }
942    }
943
944    protected final PropertyDiffDisplay getFieldXPaths(String propertyName, PropertyDiff propertyDiff,
945            Serializable leftProperty, Serializable rightProperty, boolean isDisplayAllItems,
946            boolean isDisplayItemIndexes, List<DiffFieldItemDefinition> complexFieldItemDefs) {
947
948        PropertyDiffDisplay fieldXPaths = null;
949        if (propertyDiff == null) {
950            throw new NuxeoException("The 'propertyDiff' parameter cannot be null.");
951        }
952
953        boolean isDisplayHtmlConversion = ContentDiffHelper.isDisplayHtmlConversion(leftProperty)
954                && ContentDiffHelper.isDisplayHtmlConversion(rightProperty);
955        boolean isDisplayTextConversion = ContentDiffHelper.isDisplayTextConversion(leftProperty)
956                && ContentDiffHelper.isDisplayTextConversion(rightProperty);
957
958        // Simple type
959        if (propertyDiff.isSimpleType()) {
960            SimplePropertyDiff simplePropertyDiff = (SimplePropertyDiff) propertyDiff;
961            // Keep fieldXPaths null if one of the left or right properties is
962            // empty
963            if (!StringUtils.isEmpty(simplePropertyDiff.getLeftValue())
964                    && !StringUtils.isEmpty(simplePropertyDiff.getRightValue())) {
965                fieldXPaths = new ContentDiffDisplayImpl(propertyName, simplePropertyDiff.getDifferenceType(),
966                        isDisplayHtmlConversion, isDisplayTextConversion);
967            }
968        }
969        // Content type
970        else if (propertyDiff.isContentType()) {
971            ContentPropertyDiff contentPropertyDiff = (ContentPropertyDiff) propertyDiff;
972            ContentProperty leftContent = contentPropertyDiff.getLeftContent();
973            ContentProperty rightContent = contentPropertyDiff.getRightContent();
974            // Keep fieldXPaths null if one of the left or right properties is
975            // empty
976            if (leftContent != null
977                    && rightContent != null
978                    && (!StringUtils.isEmpty(leftContent.getFilename()) && !StringUtils.isEmpty(rightContent.getFilename()))
979                    || (!StringUtils.isEmpty(leftContent.getDigest()) && !StringUtils.isEmpty(rightContent.getDigest()))) {
980                fieldXPaths = new ContentDiffDisplayImpl(propertyName, contentPropertyDiff.getDifferenceType(),
981                        isDisplayHtmlConversion, isDisplayTextConversion);
982            }
983        }
984        // Complex type
985        else if (propertyDiff.isComplexType()) {
986
987            Map<String, Serializable> leftComplexProperty = getComplexPropertyIfNotNull(leftProperty);
988            Map<String, Serializable> rightComplexProperty = getComplexPropertyIfNotNull(rightProperty);
989
990            // TODO (maybe): take into account subwidget properties
991            // 'displayAllItems' and 'displayItemIndexes'
992            // instead of inheriting them from the parent
993            // widget.
994            Map<String, PropertyDiff> complexPropertyDiffMap = ((ComplexPropertyDiff) propertyDiff).getDiffMap();
995            Map<String, Serializable> complexPropertyXPaths = new HashMap<String, Serializable>();
996            if (CollectionUtils.isEmpty(complexFieldItemDefs)) {
997                Iterator<String> complexFieldItemNamesIt = complexPropertyDiffMap.keySet().iterator();
998                while (complexFieldItemNamesIt.hasNext()) {
999                    String complexFieldItemName = complexFieldItemNamesIt.next();
1000                    setComplexPropertyXPaths(complexPropertyXPaths, complexFieldItemName,
1001                            getSubPropertyFullName(propertyName, complexFieldItemName), complexPropertyDiffMap,
1002                            leftComplexProperty, rightComplexProperty, isDisplayAllItems, isDisplayItemIndexes);
1003                }
1004            } else {
1005                for (DiffFieldItemDefinition complexFieldItemDef : complexFieldItemDefs) {
1006                    if (complexFieldItemDef.isDisplayContentDiffLinks()) {
1007                        String complexFieldItemName = complexFieldItemDef.getName();
1008                        if (complexPropertyDiffMap.containsKey(complexFieldItemName)) {
1009                            setComplexPropertyXPaths(complexPropertyXPaths, complexFieldItemName,
1010                                    getSubPropertyFullName(propertyName, complexFieldItemName), complexPropertyDiffMap,
1011                                    leftComplexProperty, rightComplexProperty, isDisplayAllItems, isDisplayItemIndexes);
1012                        }
1013                    }
1014                }
1015            }
1016            fieldXPaths = new ContentDiffDisplayImpl((Serializable) complexPropertyXPaths);
1017        }
1018        // List type
1019        else {
1020            List<Serializable> leftListProperty = getListPropertyIfNotNull(leftProperty);
1021            List<Serializable> rightListProperty = getListPropertyIfNotNull(rightProperty);
1022
1023            ListPropertyDiff listPropertyDiff = (ListPropertyDiff) propertyDiff;
1024
1025            // Get list property indexes
1026            // By default: only items that are different (ie. held by the
1027            // propertyDiff)
1028            List<Integer> listPropertyIndexes = new ArrayList<Integer>();
1029            if (isDisplayAllItems) {
1030                // All items
1031                int listPropertySize = Math.min(leftListProperty.size(), rightListProperty.size());
1032
1033                for (int index = 0; index < listPropertySize; index++) {
1034                    listPropertyIndexes.add(index);
1035                }
1036            } else {
1037                listPropertyIndexes = listPropertyDiff.getDiffIndexes();
1038            }
1039            fieldXPaths = getComplexListXPaths(propertyName, listPropertyIndexes, listPropertyDiff, leftListProperty,
1040                    rightListProperty, isDisplayAllItems, isDisplayItemIndexes);
1041        }
1042        return fieldXPaths;
1043    }
1044
1045    protected final PropertyDiffDisplay getComplexListXPaths(String propertyName, List<Integer> listPropertyIndexes,
1046            ListPropertyDiff listPropertyDiff, List<Serializable> leftListProperty,
1047            List<Serializable> rightListProperty, boolean isDisplayAllItems, boolean isDisplayItemIndexes)
1048            {
1049
1050        if (listPropertyIndexes.isEmpty()) {
1051            // TODO: add differenceType?
1052            return new ContentDiffDisplayImpl(new ArrayList<Serializable>());
1053        }
1054        boolean isComplexListWidget = isDisplayItemIndexes
1055                || (listPropertyDiff != null && listPropertyDiff.isComplexListType());
1056
1057        if (isComplexListWidget) {
1058            List<Map<String, Serializable>> listFieldXPaths = new ArrayList<Map<String, Serializable>>();
1059            for (int index : listPropertyIndexes) {
1060
1061                Map<String, Serializable> listItemXPaths = new HashMap<String, Serializable>();
1062                // Put item index if wanted
1063                if (isDisplayItemIndexes) {
1064                    listItemXPaths.put(DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD, index + 1);
1065                }
1066                PropertyDiff listItemPropertyDiff = listPropertyDiff.getDiff(index);
1067                if (listItemPropertyDiff != null) {
1068
1069                    Serializable leftListPropertyItem = null;
1070                    Serializable rightListPropertyItem = null;
1071                    if (index < leftListProperty.size()) {
1072                        leftListPropertyItem = leftListProperty.get(index);
1073                    }
1074                    if (index < rightListProperty.size()) {
1075                        rightListPropertyItem = rightListProperty.get(index);
1076                    }
1077                    Map<String, Serializable> leftComplexProperty = null;
1078                    Map<String, Serializable> rightComplexProperty = null;
1079                    if (isComplexType(leftListPropertyItem)) {
1080                        leftComplexProperty = getComplexProperty(leftListPropertyItem);
1081                    }
1082                    if (isComplexType(rightListPropertyItem)) {
1083                        rightComplexProperty = getComplexProperty(rightListPropertyItem);
1084                    }
1085
1086                    // Complex list
1087                    if (listItemPropertyDiff.isComplexType()) {
1088                        Map<String, PropertyDiff> complexPropertyDiffMap = ((ComplexPropertyDiff) listItemPropertyDiff).getDiffMap();
1089                        Iterator<String> complexPropertyItemNamesIt = complexPropertyDiffMap.keySet().iterator();
1090                        while (complexPropertyItemNamesIt.hasNext()) {
1091                            String complexPropertyItemName = complexPropertyItemNamesIt.next();
1092                            // TODO: take into account subwidget properties
1093                            // 'displayAllItems' and 'displayItemIndexes'
1094                            // instead of inheriting them from the parent
1095                            // widget.
1096                            setComplexPropertyXPaths(
1097                                    listItemXPaths,
1098                                    complexPropertyItemName,
1099                                    getSubPropertyFullName(propertyName,
1100                                            getSubPropertyFullName(String.valueOf(index), complexPropertyItemName)),
1101                                    complexPropertyDiffMap, leftComplexProperty, rightComplexProperty,
1102                                    isDisplayAllItems, isDisplayItemIndexes);
1103                        }
1104                    }
1105                    // Scalar or content list
1106                    else {
1107                        String listItemXPath = null;
1108                        // Keep listItemXPath null if one of the left or right
1109                        // properties is empty
1110                        if (leftListPropertyItem != null && rightListPropertyItem != null) {
1111                            listItemXPath = getSubPropertyFullName(propertyName, String.valueOf(index));
1112                        }
1113                        boolean isDisplayHtmlConversion = ContentDiffHelper.isDisplayHtmlConversion(leftListPropertyItem)
1114                                && ContentDiffHelper.isDisplayHtmlConversion(rightListPropertyItem);
1115                        boolean isDisplayTextConversion = ContentDiffHelper.isDisplayTextConversion(leftListPropertyItem)
1116                                && ContentDiffHelper.isDisplayTextConversion(rightListPropertyItem);
1117                        listItemXPaths.put(DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD, new ContentDiffDisplayImpl(
1118                                listItemXPath, listItemPropertyDiff.getDifferenceType(), isDisplayHtmlConversion,
1119                                isDisplayTextConversion));
1120                    }
1121                }
1122                listFieldXPaths.add(listItemXPaths);
1123            }
1124            return new ContentDiffDisplayImpl((Serializable) listFieldXPaths);
1125        } else {
1126            List<PropertyDiffDisplay> listFieldXPaths = new ArrayList<PropertyDiffDisplay>();
1127            for (int index : listPropertyIndexes) {
1128                PropertyDiffDisplay listItemXPath = null;
1129                // Keep listItemXPath null if one of the left or right
1130                // properties is empty
1131                if (index < leftListProperty.size() && index < rightListProperty.size()) {
1132                    PropertyDiff listItemPropertyDiff = null;
1133                    if (listPropertyDiff != null) {
1134                        listItemPropertyDiff = listPropertyDiff.getDiff(index);
1135                    }
1136                    DifferenceType differenceType = DifferenceType.different;
1137                    if (listItemPropertyDiff != null) {
1138                        differenceType = listItemPropertyDiff.getDifferenceType();
1139                    }
1140                    Serializable leftListPropertyItem = leftListProperty.get(index);
1141                    Serializable rightListPropertyItem = rightListProperty.get(index);
1142                    boolean isDisplayHtmlConversion = ContentDiffHelper.isDisplayHtmlConversion(leftListPropertyItem)
1143                            && ContentDiffHelper.isDisplayHtmlConversion(rightListPropertyItem);
1144                    boolean isDisplayTextConversion = ContentDiffHelper.isDisplayTextConversion(leftListPropertyItem)
1145                            && ContentDiffHelper.isDisplayTextConversion(rightListPropertyItem);
1146                    listItemXPath = new ContentDiffDisplayImpl(getSubPropertyFullName(propertyName,
1147                            String.valueOf(index)), differenceType, isDisplayHtmlConversion, isDisplayTextConversion);
1148                }
1149                listFieldXPaths.add(listItemXPath);
1150            }
1151            return new ContentDiffDisplayImpl((Serializable) listFieldXPaths);
1152        }
1153    }
1154
1155    /**
1156     * Sets the complex property xpaths.
1157     *
1158     * @param complexPropertyXPaths the complex property xpaths
1159     * @param complexFieldItemName the complex field item name
1160     * @param subPropertyFullName the sub property full name
1161     * @param complexPropertyDiffMap the complex property diff map
1162     * @param leftComplexProperty the left complex property
1163     * @param rightComplexProperty the right complex property
1164     * @param isDisplayAllItems the is display all items
1165     * @param isDisplayItemIndexes the is display item indexes
1166     */
1167    protected void setComplexPropertyXPaths(Map<String, Serializable> complexPropertyXPaths,
1168            String complexFieldItemName, String subPropertyFullName, Map<String, PropertyDiff> complexPropertyDiffMap,
1169            Map<String, Serializable> leftComplexProperty, Map<String, Serializable> rightComplexProperty,
1170            boolean isDisplayAllItems, boolean isDisplayItemIndexes) {
1171
1172        Serializable leftComplexPropertyItemValue = null;
1173        Serializable rightComplexPropertyItemValue = null;
1174        if (leftComplexProperty != null) {
1175            leftComplexPropertyItemValue = leftComplexProperty.get(complexFieldItemName);
1176        }
1177        if (rightComplexProperty != null) {
1178            rightComplexPropertyItemValue = rightComplexProperty.get(complexFieldItemName);
1179        }
1180        complexPropertyXPaths.put(
1181                complexFieldItemName,
1182                getFieldXPaths(subPropertyFullName, complexPropertyDiffMap.get(complexFieldItemName),
1183                        leftComplexPropertyItemValue, rightComplexPropertyItemValue, isDisplayAllItems,
1184                        isDisplayItemIndexes, null));
1185    }
1186
1187    protected boolean isListType(Serializable property) {
1188
1189        return property instanceof List<?> || property instanceof Serializable[];
1190    }
1191
1192    protected boolean isComplexType(Serializable property) {
1193
1194        return property instanceof Map<?, ?>;
1195    }
1196
1197    /**
1198     * Casts or convert a {@link Serializable} property to {@link List <Serializable>}.
1199     *
1200     * @param property the property
1201     * @return the list property
1202     * @throws ClassCastException if the {@code property} is not a {@link List <Serializable>} nor an array.
1203     */
1204    @SuppressWarnings("unchecked")
1205    protected List<Serializable> getListProperty(Serializable property) {
1206        List<Serializable> listProperty;
1207        if (property instanceof List<?>) { // List
1208            listProperty = (List<Serializable>) property;
1209        } else { // Array
1210            listProperty = Arrays.asList((Serializable[]) property);
1211        }
1212        return listProperty;
1213    }
1214
1215    /**
1216     * Gets the list property if the {@code property} is not null.
1217     *
1218     * @param property the property
1219     * @return the list property if the {@code property} is not null, null otherwise
1220     * @throws NuxeoException if the {@code property} is not a list.
1221     */
1222    protected List<Serializable> getListPropertyIfNotNull(Serializable property) {
1223
1224        if (property != null) {
1225            if (!isListType(property)) {
1226                throw new NuxeoException(
1227                        "Tryed to get a list property from a Serializable property that is not a list, this is inconsistent.");
1228            }
1229            return getListProperty(property);
1230        }
1231        return null;
1232    }
1233
1234    /**
1235     * Casts a {@link Serializable} property to {@link Map<String, Serializable>}.
1236     *
1237     * @param property the property
1238     * @return the complex property
1239     * @throws ClassCastException if the {@code property} is not a {@link Map <String, Serializable>}.
1240     */
1241    @SuppressWarnings("unchecked")
1242    protected Map<String, Serializable> getComplexProperty(Serializable property) {
1243        return (Map<String, Serializable>) property;
1244    }
1245
1246    /**
1247     * Gets the complex property if the {@code property} is not null.
1248     *
1249     * @param property the property
1250     * @return the complex property if the {@code property} is not null, null otherwise
1251     * @throws NuxeoException if the {@code property} is not a list.
1252     */
1253    protected Map<String, Serializable> getComplexPropertyIfNotNull(Serializable property) {
1254
1255        if (property != null) {
1256            if (!isComplexType(property)) {
1257                throw new NuxeoException(
1258                        "Tryed to get a complex property from a Serializable property that is not a map, this is inconsistent.");
1259            }
1260            return getComplexProperty(property);
1261        }
1262        return null;
1263    }
1264
1265    // TODO: better separate regular and contentDiffLinksWidget cases (call
1266    // submethods)
1267    protected final WidgetDefinition getWidgetDefinition(String category, String propertyName, String propertyType,
1268            Field field, List<DiffFieldItemDefinition> complexFieldItemDefs, boolean isContentDiffLinksWidget)
1269            {
1270
1271        boolean isGeneric = false;
1272        boolean isCloned = false;
1273
1274        WidgetDefinition wDef = null;
1275        if (!isContentDiffLinksWidget) {
1276            // Look for a specific widget in the "diff" category named with the
1277            // property name
1278            wDef = getLayoutStore().getWidgetDefinition(category, propertyName);
1279            if (wDef == null) {
1280                isGeneric = true;
1281                // Fallback on a generic widget in the "diff" category named
1282                // with the property type
1283                wDef = getLayoutStore().getWidgetDefinition(category, propertyType);
1284                if (wDef == null) {
1285                    throw new NuxeoException(
1286                            String.format(
1287                                    "Could not find any specific widget named '%s', nor any generic widget named '%s'. Please make sure at least a generic widget is defined for this type.",
1288                                    propertyName, propertyType));
1289                }
1290            }
1291        } else {
1292            isGeneric = true;
1293            if (PropertyType.isSimpleType(propertyType) || PropertyType.isContentType(propertyType)) {
1294                wDef = getLayoutStore().getWidgetDefinition(category, CONTENT_DIFF_LINKS_WIDGET_NAME);
1295                if (wDef == null) {
1296                    throw new NuxeoException(
1297                            String.format(
1298                                    "Could not find any generic widget named '%s'. Please make sure a generic widget is defined with this name.",
1299                                    CONTENT_DIFF_LINKS_WIDGET_NAME));
1300                }
1301            } else {
1302                // Get the generic widget in the "diff" category named with
1303                // the property type
1304                wDef = getLayoutStore().getWidgetDefinition(category, propertyType);
1305                if (wDef == null) {
1306                    throw new NuxeoException(
1307                            String.format(
1308                                    "Could not find any generic widget named '%s'. Please make sure a generic widget is defined for this type.",
1309                                    propertyType));
1310                }
1311            }
1312        }
1313
1314        if (isGeneric) {
1315            // Clone widget definition
1316            wDef = wDef.clone();
1317            isCloned = true;
1318            // Set widget name
1319            String widgetName = propertyName;
1320            if (isContentDiffLinksWidget) {
1321                widgetName += CONTENT_DIFF_LINKS_WIDGET_NAME_SUFFIX;
1322            }
1323            wDef.setName(widgetName);
1324
1325            // Set labels
1326            Map<String, String> labels = new HashMap<String, String>();
1327            labels.put(BuiltinModes.ANY, DIFF_WIDGET_LABEL_PREFIX + getPropertyLabel(propertyName));
1328            wDef.setLabels(labels);
1329
1330            // Set translated
1331            wDef.setTranslated(true);
1332        }
1333
1334        // Set field definitions if generic or specific and not already set in
1335        // widget definition
1336        if (isGeneric || !isFieldDefinitions(wDef)) {
1337
1338            String fieldName = propertyName;
1339            if (field != null) {
1340                fieldName = field.getName().getLocalName();
1341            }
1342
1343            FieldDefinition[] fieldDefinitions;
1344            if (isContentDiffLinksWidget) {
1345                fieldDefinitions = new FieldDefinition[4];
1346                fieldDefinitions[0] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1347                        DIFF_WIDGET_FIELD_DEFINITION_VALUE));
1348                fieldDefinitions[1] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1349                        DIFF_WIDGET_FIELD_DEFINITION_DIFFERENCE_TYPE));
1350                fieldDefinitions[2] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1351                        DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_HTML_CONVERSION));
1352                fieldDefinitions[3] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1353                        DIFF_WIDGET_FIELD_DEFINITION_DISPLAY_TEXT_CONVERSION));
1354            } else {
1355                int fieldCount = 2;
1356                if (PropertyType.isContentType(propertyType) || ("note:note".equals(propertyName))) {
1357                    fieldCount = 3;
1358                }
1359                fieldDefinitions = new FieldDefinition[fieldCount];
1360                fieldDefinitions[0] = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1361                        DIFF_WIDGET_FIELD_DEFINITION_VALUE));
1362
1363                FieldDefinition styleClassFieldDef = new FieldDefinitionImpl(null, getFieldDefinition(fieldName,
1364                        DIFF_WIDGET_FIELD_DEFINITION_STYLE_CLASS));
1365                if (PropertyType.isContentType(propertyType)) {
1366                    fieldDefinitions[1] = new FieldDefinitionImpl(null, getFieldDefinition(
1367                            getFieldDefinition(fieldName, DIFF_WIDGET_FIELD_DEFINITION_VALUE),
1368                            DIFF_WIDGET_FIELD_DEFINITION_FILENAME));
1369                    fieldDefinitions[2] = styleClassFieldDef;
1370                } else if ("note:note".equals(propertyName)) {
1371                    fieldDefinitions[1] = new FieldDefinitionImpl(null, getFieldDefinition("note:mime_type",
1372                            DIFF_WIDGET_FIELD_DEFINITION_VALUE));
1373                    fieldDefinitions[2] = styleClassFieldDef;
1374                } else {
1375                    fieldDefinitions[1] = styleClassFieldDef;
1376                }
1377            }
1378
1379            // Clone if needed
1380            if (!isCloned) {
1381                wDef = wDef.clone();
1382                isCloned = true;
1383            }
1384            wDef.setFieldDefinitions(fieldDefinitions);
1385        }
1386
1387        // Set subwidgets if not already set
1388        if (!isSubWidgets(wDef)) {
1389            if (PropertyType.isListType(propertyType) || PropertyType.isComplexType(propertyType)) {
1390
1391                Field declaringField = field;
1392                if (declaringField == null) {
1393                    declaringField = ComplexPropertyHelper.getField(getPropertySchema(propertyName),
1394                            getPropertyField(propertyName));
1395                }
1396                // Clone if needed
1397                if (!isCloned) {
1398                    wDef = wDef.clone();
1399                    isCloned = true;
1400                }
1401                wDef.setSubWidgetDefinitions(getSubWidgetDefinitions(category, propertyName, propertyType,
1402                        declaringField, complexFieldItemDefs, isDisplayItemIndexes(wDef), isContentDiffLinksWidget));
1403            }
1404        }
1405
1406        return wDef;
1407    }
1408
1409    protected final boolean isSubWidgets(WidgetDefinition wDef) {
1410
1411        WidgetDefinition[] subWidgetDefs = wDef.getSubWidgetDefinitions();
1412        return subWidgetDefs != null && subWidgetDefs.length > 0;
1413    }
1414
1415    protected final boolean isFieldDefinitions(WidgetDefinition wDef) {
1416
1417        FieldDefinition[] fieldDefs = wDef.getFieldDefinitions();
1418        return fieldDefs != null && fieldDefs.length > 0;
1419    }
1420
1421    protected final String getFieldDefinition(String fieldName, String subPropertyName) {
1422
1423        return fieldName + "/" + subPropertyName;
1424    }
1425
1426    protected final WidgetDefinition[] getSubWidgetDefinitions(String category, String propertyName,
1427            String propertyType, Field field, List<DiffFieldItemDefinition> complexFieldItemDefs,
1428            boolean isDisplayItemIndexes, boolean isContentDiffLinks) {
1429
1430        WidgetDefinition[] subWidgetDefs = null;
1431        // Complex
1432        if (PropertyType.isComplexType(propertyType)) {
1433            subWidgetDefs = getComplexSubWidgetDefinitions(category, propertyName, field, complexFieldItemDefs, false,
1434                    isContentDiffLinks);
1435        }
1436        // Scalar or content list
1437        else if (PropertyType.isScalarListType(propertyType) || PropertyType.isContentListType(propertyType)) {
1438            Field listFieldItem = ComplexPropertyHelper.getListFieldItem(field);
1439            subWidgetDefs = initSubWidgetDefinitions(isDisplayItemIndexes, 1);
1440            subWidgetDefs[subWidgetDefs.length - 1] = getWidgetDefinition(category,
1441                    getSubPropertyFullName(propertyName, listFieldItem.getName().getLocalName()),
1442                    ComplexPropertyHelper.getFieldType(listFieldItem), new FieldImpl(new QName(
1443                            DIFF_LIST_WIDGET_VALUE_SUBWIDGET_FIELD), field.getType(), listFieldItem.getType()), null,
1444                    isContentDiffLinks);
1445        }
1446        // Complex list
1447        else if (PropertyType.isComplexListType(propertyType)) {
1448            Field listFieldItem = ComplexPropertyHelper.getListFieldItem(field);
1449            subWidgetDefs = getComplexSubWidgetDefinitions(category, propertyName, listFieldItem, complexFieldItemDefs,
1450                    isDisplayItemIndexes, isContentDiffLinks);
1451        }
1452        return subWidgetDefs;
1453    }
1454
1455    protected final WidgetDefinition[] getComplexSubWidgetDefinitions(String category, String propertyName,
1456            Field field, List<DiffFieldItemDefinition> complexFieldItemDefs, boolean isDisplayItemIndexes,
1457            boolean isContentDiffLinks) {
1458
1459        WidgetDefinition[] subWidgetDefs;
1460        int subWidgetIndex = isDisplayItemIndexes ? 1 : 0;
1461
1462        if (CollectionUtils.isEmpty(complexFieldItemDefs)) {
1463            List<Field> complexFieldItems = ComplexPropertyHelper.getComplexFieldItems(field);
1464            subWidgetDefs = initSubWidgetDefinitions(isDisplayItemIndexes, complexFieldItems.size());
1465
1466            for (Field complexFieldItem : complexFieldItems) {
1467                subWidgetDefs[subWidgetIndex] = getWidgetDefinition(category,
1468                        getSubPropertyFullName(propertyName, complexFieldItem.getName().getLocalName()),
1469                        ComplexPropertyHelper.getFieldType(complexFieldItem), complexFieldItem, null,
1470                        isContentDiffLinks);
1471                subWidgetIndex++;
1472            }
1473        } else {
1474            int subWidgetCount = complexFieldItemDefs.size();
1475            // Only add a subwidget for the items marked to display the content
1476            // diff links
1477            if (isContentDiffLinks) {
1478                subWidgetCount = 0;
1479                for (DiffFieldItemDefinition complexFieldItemDef : complexFieldItemDefs) {
1480                    if (complexFieldItemDef.isDisplayContentDiffLinks()) {
1481                        subWidgetCount++;
1482                    }
1483                }
1484            }
1485            subWidgetDefs = initSubWidgetDefinitions(isDisplayItemIndexes, subWidgetCount);
1486
1487            for (DiffFieldItemDefinition complexFieldItemDef : complexFieldItemDefs) {
1488                if (!isContentDiffLinks || complexFieldItemDef.isDisplayContentDiffLinks()) {
1489                    String complexFieldItemName = complexFieldItemDef.getName();
1490                    Field complexFieldItem = ComplexPropertyHelper.getComplexFieldItem(field, complexFieldItemName);
1491                    if (complexFieldItem != null) {
1492                        subWidgetDefs[subWidgetIndex] = getWidgetDefinition(category,
1493                                getSubPropertyFullName(propertyName, complexFieldItemName),
1494                                ComplexPropertyHelper.getFieldType(complexFieldItem), complexFieldItem, null,
1495                                isContentDiffLinks);
1496                        subWidgetIndex++;
1497                    }
1498                }
1499            }
1500        }
1501        return subWidgetDefs;
1502    }
1503
1504    protected final WidgetDefinition[] initSubWidgetDefinitions(boolean isDisplayItemIndexes, int subWidgetCount) {
1505
1506        WidgetDefinition[] subWidgetDefs;
1507        if (isDisplayItemIndexes) {
1508            subWidgetDefs = new WidgetDefinition[subWidgetCount + 1];
1509            subWidgetDefs[0] = getIndexSubwidgetDefinition();
1510        } else {
1511            subWidgetDefs = new WidgetDefinition[subWidgetCount];
1512        }
1513
1514        return subWidgetDefs;
1515    }
1516
1517    protected final WidgetDefinition getIndexSubwidgetDefinition() {
1518
1519        FieldDefinition[] fieldDefinitions = { new FieldDefinitionImpl(null, DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD) };
1520
1521        return new WidgetDefinitionImpl(DIFF_LIST_WIDGET_INDEX_SUBWIDGET_FIELD, DIFF_LIST_WIDGET_INDEX_SUBWIDGET_TYPE,
1522                DIFF_LIST_WIDGET_INDEX_SUBWIDGET_LABEL, null, true, null, Arrays.asList(fieldDefinitions), null, null);
1523    }
1524
1525    /**
1526     * Gets the property name.
1527     *
1528     * @param schema the schema
1529     * @param field the field
1530     * @return the property name
1531     */
1532    protected final String getPropertyName(String schema, String field) {
1533
1534        StringBuilder sb = new StringBuilder();
1535        if (!StringUtils.isEmpty(schema)) {
1536            sb.append(schema);
1537            sb.append(":");
1538        }
1539        sb.append(field);
1540        return sb.toString();
1541    }
1542
1543    protected final String getSubPropertyFullName(String basePropertyName, String subPropertyName) {
1544
1545        if (StringUtils.isEmpty(subPropertyName)) {
1546            return basePropertyName;
1547        }
1548        StringBuilder sb = new StringBuilder(basePropertyName);
1549        sb.append("/");
1550        sb.append(subPropertyName);
1551        return sb.toString();
1552    }
1553
1554    protected final String getPropertySchema(String propertyName) {
1555
1556        int indexOfColon = propertyName.indexOf(':');
1557        if (indexOfColon > -1) {
1558            return propertyName.substring(0, indexOfColon);
1559        }
1560        return null;
1561    }
1562
1563    protected final String getPropertyField(String propertyName) {
1564
1565        int indexOfColon = propertyName.indexOf(':');
1566        if (indexOfColon > -1 && indexOfColon < propertyName.length() - 1) {
1567            return propertyName.substring(indexOfColon + 1);
1568        }
1569        return propertyName;
1570    }
1571
1572    protected final String getPropertyLabel(String propertyName) {
1573
1574        return propertyName.replaceAll(":", ".").replaceAll("/", ".");
1575    }
1576
1577    /**
1578     * Gets the schema manager.
1579     *
1580     * @return the schema manager
1581     */
1582    protected final SchemaManager getSchemaManager() {
1583        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
1584        if (schemaManager == null) {
1585            throw new NuxeoException("SchemaManager service is null.");
1586        }
1587        return schemaManager;
1588    }
1589
1590    /**
1591     * Gets the layout store service.
1592     *
1593     * @return the layout store service
1594     */
1595    protected final LayoutStore getLayoutStore() {
1596        LayoutStore layoutStore = Framework.getService(LayoutStore.class);
1597        if (layoutStore == null) {
1598            throw new NuxeoException("LayoutStore service is null.");
1599        }
1600        return layoutStore;
1601    }
1602}