15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)package org.chromium.sync.internal_api.pub.base;
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.util.Log;
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import com.google.ipc.invalidation.external.client.types.ObjectId;
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import com.google.protos.ipc.invalidation.Types;
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
12116680a4aac90f2aa7413d9095a592090648e557Ben Murdochimport org.chromium.base.FieldTrialList;
131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport org.chromium.base.VisibleForTesting;
14116680a4aac90f2aa7413d9095a592090648e557Ben Murdochimport org.chromium.base.library_loader.LibraryLoader;
15116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.util.Collection;
173551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import java.util.EnumSet;
183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)import java.util.HashSet;
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import java.util.Set;
202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * The model types that are synced in Chrome for Android.
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)public enum ModelType {
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * An autofill object.
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    AUTOFILL("AUTOFILL"),
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * An autofill profile object.
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    AUTOFILL_PROFILE("AUTOFILL_PROFILE"),
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * A bookmark folder or a bookmark URL object.
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    BOOKMARK("BOOKMARK"),
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * Flags to enable experimental features.
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    EXPERIMENTS("EXPERIMENTS"),
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * An object representing a set of Nigori keys.
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    NIGORI("NIGORI"),
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * A password entry.
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    PASSWORD("PASSWORD"),
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * An object representing a browser session or tab.
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    SESSION("SESSION"),
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * A typed_url folder or a typed_url object.
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
56c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    TYPED_URL("TYPED_URL"),
57c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    /**
58c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * A history delete directive object.
59c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     */
60c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    HISTORY_DELETE_DIRECTIVE("HISTORY_DELETE_DIRECTIVE"),
61c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    /**
62c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * A device info object.
63c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     */
64c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    DEVICE_INFO("DEVICE_INFO"),
65c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    /**
66c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * A proxy tabs object (placeholder for sessions).
67c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     */
68ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    PROXY_TABS("NULL", true),
69c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    /**
70c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * A favicon image object.
71c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     */
72c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    FAVICON_IMAGE("FAVICON_IMAGE"),
73c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    /**
74c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     * A favicon tracking object.
75c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)     */
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    FAVICON_TRACKING("FAVICON_TRACKING"),
771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    /**
781320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci     * A supervised user setting object. The old name "managed user" is used for backwards
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci     * compatibility.
801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci     */
811320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    MANAGED_USER_SETTING("MANAGED_USER_SETTING");
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /** Special type representing all possible types. */
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public static final String ALL_TYPES_TYPE = "ALL_TYPES";
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
869ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch    private static final String TAG = "ModelType";
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private final String mModelType;
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
90ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    private final boolean mNonInvalidationType;
91ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
92ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    ModelType(String modelType, boolean nonInvalidationType) {
931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        assert nonInvalidationType || name().equals(modelType);
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        mModelType = modelType;
95ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        mNonInvalidationType = nonInvalidationType;
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
98ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    ModelType(String modelType) {
99ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        this(modelType, false);
100ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    }
101ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
102116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    private boolean isNonInvalidationType() {
1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if ((this == SESSION || this == FAVICON_TRACKING) && LibraryLoader.isInitialized()) {
104116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        return FieldTrialList
105116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch            .findFullName("AndroidSessionNotifications")
106116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch            .equals("Disabled");
107116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      }
108116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      return mNonInvalidationType;
109116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    }
110116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
111ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    /**
112ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * Returns the {@link ObjectId} representation of this {@link ModelType}.
113ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     *
114ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * This should be used with caution, since it converts even {@link ModelType} instances with
115ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * |mNonInvalidationType| set. For automatically stripping such {@link ModelType} entries out,
116ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * use {@link ModelType#modelTypesToObjectIds(java.util.Set)} instead.
117ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     */
118ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    @VisibleForTesting
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public ObjectId toObjectId() {
1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return ObjectId.newInstance(Types.ObjectSource.CHROME_SYNC, mModelType.getBytes());
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public static ModelType fromObjectId(ObjectId objectId) {
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        try {
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return valueOf(new String(objectId.getName()));
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        } catch (IllegalArgumentException e) {
1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return null;
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * Converts string representations of types to sync to {@link ModelType}s.
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * <p>
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * If {@code syncTypes} contains {@link #ALL_TYPES_TYPE}, then the returned
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * set contains all values of the {@code ModelType} enum.
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * <p>
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * Otherwise, the returned set contains the {@code ModelType} values for all elements of
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * {@code syncTypes} for which {@link ModelType#valueOf(String)} successfully returns; other
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     * elements are dropped.
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)     */
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public static Set<ModelType> syncTypesToModelTypes(Collection<String> syncTypes) {
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if (syncTypes.contains(ALL_TYPES_TYPE)) {
1433551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            return EnumSet.allOf(ModelType.class);
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        } else {
1453551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)            Set<ModelType> modelTypes = new HashSet<ModelType>(syncTypes.size());
1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            for (String syncType : syncTypes) {
1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                try {
1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    modelTypes.add(valueOf(syncType));
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                } catch (IllegalArgumentException exception) {
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    // Drop invalid sync types.
1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    Log.w(TAG, "Could not translate sync type to model type: " + syncType);
1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                }
1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            }
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            return modelTypes;
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
158ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    /**
159ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * Converts a set of sync types {@link String} to a set of {@link ObjectId}.
160ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     *
161ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * This strips out any {@link ModelType} that is not an invalidation type.
162ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     */
163ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    public static Set<ObjectId> syncTypesToObjectIds(Collection<String> syncTypes) {
164ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch        return modelTypesToObjectIds(syncTypesToModelTypes(syncTypes));
165ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    }
166ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch
167ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch    /**
168ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * Converts a set of {@link ModelType} to a set of {@link ObjectId}.
169ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     *
170ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     * This strips out any {@link ModelType} that is not an invalidation type.
171ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16Ben Murdoch     */
1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    public static Set<ObjectId> modelTypesToObjectIds(Set<ModelType> modelTypes) {
1731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Set<ModelType> filteredModelTypes = filterOutNonInvalidationTypes(modelTypes);
1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Set<ObjectId> objectIds = new HashSet<ObjectId>(filteredModelTypes.size());
1751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        for (ModelType modelType : filteredModelTypes) {
1761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            objectIds.add(modelType.toObjectId());
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return objectIds;
1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /** Converts a set of {@link ModelType} to a set of string names. */
1821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    public static Set<String> modelTypesToSyncTypesForTest(Set<ModelType> modelTypes) {
1833551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)        Set<String> objectIds = new HashSet<String>(modelTypes.size());
1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        for (ModelType modelType : modelTypes) {
1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            objectIds.add(modelType.toString());
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        }
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        return objectIds;
1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    /** Filters out non-invalidation types from a set of {@link ModelType}. */
1911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    @VisibleForTesting
1921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    public static Set<ModelType> filterOutNonInvalidationTypes(Set<ModelType> modelTypes) {
1931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        Set<ModelType> filteredTypes = new HashSet<ModelType>(modelTypes.size());
1941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        for (ModelType modelType : modelTypes) {
1951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            if (!modelType.isNonInvalidationType()) {
1961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                filteredTypes.add(modelType);
1971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            }
1981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }
1991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return filteredTypes;
2001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }
2011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
203