com.codenvy.cli.preferences.file.JsonPreferences.java Source code

Java tutorial

Introduction

Here is the source code for com.codenvy.cli.preferences.file.JsonPreferences.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2014 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package com.codenvy.cli.preferences.file;

import com.codenvy.cli.preferences.Preferences;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.type.MapLikeType;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.codenvy.cli.preferences.file.LifecycleEvent.CREATE;
import static com.codenvy.cli.preferences.file.LifecycleEvent.DELETE;
import static com.codenvy.cli.preferences.file.LifecycleEvent.MERGE;

/**
 * Real implementation of {@link Preferences} based on Jackson to be able to map and unmap stored objects to a preferences tree.
 *
 * @author Stphane Daviet
 */
public class JsonPreferences implements Preferences, LifecycleCallback {

    private List<LifecycleCallback> callbackList;

    private final ConcurrentMap<String, Object> innerPreferences;

    private final ObjectMapper mapper;

    private final MapLikeType mapType;

    private final Collection<Class<?>> simpleTypes = Arrays.asList(new Class<?>[] { Byte.class, Character.class,
            Short.class, Integer.class, Long.class, Boolean.class, Float.class, Double.class, Void.class });

    protected JsonPreferences() {
        this(new HashMap<String, Object>());
    }

    @JsonCreator
    protected JsonPreferences(Map<String, Object> innerPreferences) {
        this.callbackList = new ArrayList<>();
        this.innerPreferences = new ConcurrentHashMap<String, Object>(innerPreferences);

        this.mapper = new ObjectMapper();
        this.mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        this.mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
        this.mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        this.mapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
        this.mapper.setVisibility(PropertyAccessor.IS_GETTER, Visibility.PUBLIC_ONLY);

        this.mapType = this.mapper.getTypeFactory().constructMapType(ConcurrentMap.class, String.class,
                Object.class);
    }

    @JsonValue
    public Map<String, Object> getInnerPreferences() {
        return innerPreferences;
    }

    protected Object get(String key) {
        return innerPreferences.get(key);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(String key, Class<T> clazz) {
        if (!shouldMapUnmap(clazz)) {
            return (T) innerPreferences.get(key);
        }
        return mapper.convertValue(innerPreferences.get(key), clazz);
    }

    @Override
    public void put(String key, Object value) {
        put(key, value, true);
        notify(CREATE);
    }

    @Override
    public void merge(String key, Object value) {
        put(key, value, false);
        notify(MERGE);
    }

    protected void put(String key, Object value, boolean overwrite) {
        Object previousValue = innerPreferences.get(key);
        if (previousValue != null && !overwrite) {
            ObjectReader updater = mapper.readerForUpdating(previousValue);
            try {
                updater.readValue(this.mapper.valueToTree(value));
            } catch (IllegalArgumentException | IOException e) {
                throw new IllegalStateException("Unable to put the value", e);
            }
        } else {
            if (!shouldMapUnmap(value.getClass())) {
                innerPreferences.put(key, value);
            } else {
                innerPreferences.put(key, this.mapper.convertValue(value, mapType));
            }
        }
    }

    @Override
    public void delete(String key) {
        innerPreferences.remove(key);
        notify(DELETE);
    }

    @Override
    public Preferences path(String key) {
        return path(key, true);
    }

    @Override
    public Preferences walk(String key) {
        return path(key, false);
    }

    @Override
    public boolean pathExists(String key) {
        return innerPreferences.containsKey(key);
    }

    protected Preferences path(String key, boolean create) {
        Object value = innerPreferences.get(key);
        if (value == null) {
            if (!create) {
                return null;
            }
            value = new JsonPreferences();
            ((JsonPreferences) value).addCallback(this);
            innerPreferences.put(key, value);
            return (JsonPreferences) value;
        } else if (value instanceof JsonPreferences) {
            return (JsonPreferences) value;
        } else if (value instanceof Map) {
            @SuppressWarnings("unchecked")
            JsonPreferences deepInnerPreferences = new JsonPreferences((Map<String, Object>) value);
            deepInnerPreferences.addCallback(this);
            innerPreferences.put(key, deepInnerPreferences);
            return deepInnerPreferences;
        } else {
            return this;
        }
    }

    private boolean shouldMapUnmap(Class<?> clazz) {
        return !(simpleTypes.contains(clazz) || clazz.isPrimitive() || clazz.isArray()
                || Enum.class.isAssignableFrom(clazz) || Collection.class.isAssignableFrom(clazz));
    }

    protected void addCallback(LifecycleCallback lifecycleCallback) {
        callbackList.add(lifecycleCallback);
    }

    protected void sendNotification(LifecycleEvent lifecycleEvent) {
        for (LifecycleCallback callback : callbackList) {
            callback.notify(lifecycleEvent);
        }
    }

    @Override
    public void notify(LifecycleEvent lifecycleEvent) {
        // notify listeners
        sendNotification(lifecycleEvent);
    }
}