/*******************************************************************************
* Copyright (c) 2012-2015 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 org.eclipse.che.dto.server;
import org.eclipse.che.commons.lang.cache.Cache;
import org.eclipse.che.commons.lang.cache.LoadingValueSLRUCache;
import org.eclipse.che.commons.lang.cache.SynchronizedCache;
import org.eclipse.che.commons.lang.reflect.ParameterizedTypeImpl;
import org.eclipse.che.dto.shared.DTO;
import org.eclipse.che.dto.shared.JsonArray;
import org.eclipse.che.dto.shared.JsonStringMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
* Provides implementations of DTO interfaces.
*
* @author andrew00x
*/
public final class DtoFactory {
private static final Gson gson = new GsonBuilder().serializeNulls().create();
private static final Cache<Type, ParameterizedType> listTypeCache = new SynchronizedCache<>(
new LoadingValueSLRUCache<Type, ParameterizedType>(16, 16) {
@Override
protected ParameterizedType loadValue(Type type) {
return ParameterizedTypeImpl.newParameterizedType(List.class, type);
}
});
private static final Cache<Type, ParameterizedType> mapTypeCache = new SynchronizedCache<>(
new LoadingValueSLRUCache<Type, ParameterizedType>(16, 16) {
@Override
protected ParameterizedType loadValue(Type type) {
return ParameterizedTypeImpl.newParameterizedType(Map.class, String.class, type);
}
});
private static final DtoFactory INSTANCE = new DtoFactory();
public static DtoFactory getInstance() {
return INSTANCE;
}
private final Map<Class<?>, DtoProvider<?>> dtoInterface2Providers = new ConcurrentHashMap<>();
// Additional mapping for implementation of DTO interfaces.
// It helps avoid reflection when need create copy of exited DTO instance.
private final Map<Class<?>, DtoProvider<?>> dtoImpl2Providers = new ConcurrentHashMap<>();
/**
* Created deep copy of DTO object.
*
* @param origin
* origin DTO object
* @return copy
* @throws IllegalArgumentException
* if specified object doesn't implement DTO interface annotated with {@link org.eclipse.che.dto.shared.DTO @DTO} or if
* specified instance implements more than one interface annotated with {@link org.eclipse.che.dto.shared.DTO @DTO}
*/
@SuppressWarnings("unchecked")
public <T> T clone(T origin) {
final Class<?> implClass = origin.getClass();
DtoProvider provider = dtoImpl2Providers.get(implClass);
if (provider == null) {
Class<?> dtoInterface = null;
Class<?>[] interfaces = implClass.getInterfaces();
if (interfaces.length == 0) {
return null;
}
for (Class<?> i : interfaces) {
if (i.isAnnotationPresent(DTO.class)) {
if (dtoInterface != null) {
throw new IllegalArgumentException("Unable determine DTO interface. Type " + implClass.getName() +
" implements or extends more than one interface annotated with @DTO annotation.");
}
dtoInterface = i;
}
}
if (dtoInterface != null) {
provider = getDtoProvider(dtoInterface);
}
}
if (provider == null) {
throw new IllegalArgumentException("Unknown DTO type " + implClass);
}
return (T)provider.clone(origin);
}
public <T> String toJson(T dto) {
if (dto instanceof JsonSerializable) {
return ((JsonSerializable)dto).toJson();
}
throw new IllegalArgumentException("JsonSerializable instance required. ");
}
/**
* Creates new instance of class which implements specified DTO interface.
*
* @param dtoInterface
* DTO interface
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
*/
public <T> T createDto(Class<T> dtoInterface) {
return getDtoProvider(dtoInterface).newInstance();
}
//
/**
* Creates new instance of class which implements specified DTO interface, parses specified JSON string and uses parsed data for
* initializing fields of DTO object.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
*/
public <T> T createDtoFromJson(String json, Class<T> dtoInterface) {
return getDtoProvider(dtoInterface).fromJson(json);
}
/**
* Creates new instance of class which implements specified DTO interface, parses specified JSON data and uses parsed data for
* initializing fields of DTO object.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
* @throws IOException
* if an i/o error occurs
*/
public <T> T createDtoFromJson(Reader json, Class<T> dtoInterface) throws IOException {
DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(json);
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
return dtoProvider.fromJson(sb.toString());
}
/**
* Creates new instance of class which implements specified DTO interface, parses specified JSON data and uses parsed data for
* initializing fields of DTO object.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
* @throws IOException
* if an i/o error occurs
*/
public <T> T createDtoFromJson(InputStream json, Class<T> dtoInterface) throws IOException {
return createDtoFromJson(new InputStreamReader(json), dtoInterface);
}
//
/**
* Parses the JSON data from the specified sting into list of objects of the specified type.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @return list of DTO
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
*/
public <T> JsonArray<T> createListDtoFromJson(String json, Class<T> dtoInterface) {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final List<JsonElement> list = gson.fromJson(json, listTypeCache.get(JsonElement.class));
final List<T> result = new ArrayList<>(list.size());
for (JsonElement e : list) {
result.add(dtoProvider.fromJson(e));
}
return new JsonArrayImpl<>(result);
}
/**
* Parses the JSON data from the specified reader into list of objects of the specified type.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @return list of DTO
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
*/
public <T> JsonArray<T> createListDtoFromJson(Reader json, Class<T> dtoInterface) throws IOException {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final List<JsonElement> list;
try {
list = gson.fromJson(json, listTypeCache.get(JsonElement.class));
} catch (JsonSyntaxException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException)cause;
}
throw e;
}
final List<T> result = new ArrayList<>(list.size());
for (JsonElement e : list) {
result.add(dtoProvider.fromJson(e));
}
return new JsonArrayImpl<>(result);
}
/**
* Parses the JSON data from the specified stream into list of objects of the specified type.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @return list of DTO
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
* @throws IOException
* if an i/o error occurs
*/
public <T> JsonArray<T> createListDtoFromJson(InputStream json, Class<T> dtoInterface) throws IOException {
return createListDtoFromJson(new InputStreamReader(json), dtoInterface);
}
//
/**
* Parses the JSON data from the specified sting into map of objects of the specified type.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @return map of DTO
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
*/
public <T> JsonStringMap<T> createMapDtoFromJson(String json, Class<T> dtoInterface) {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final Map<String, JsonElement> map = gson.fromJson(json, mapTypeCache.get(JsonElement.class));
final Map<String, T> result = new LinkedHashMap<>(map.size());
for (Map.Entry<String, JsonElement> e : map.entrySet()) {
result.put(e.getKey(), dtoProvider.fromJson(e.getValue()));
}
return new JsonStringMapImpl<>(result);
}
/**
* Parses the JSON data from the specified reader into map of objects of the specified type.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @return map of DTO
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
* @throws IOException
* if an i/o error occurs
*/
@SuppressWarnings("unchecked")
public <T> JsonStringMap<T> createMapDtoFromJson(Reader json, Class<T> dtoInterface) throws IOException {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final Map<String, JsonElement> map;
try {
map = gson.fromJson(json, mapTypeCache.get(JsonElement.class));
} catch (JsonSyntaxException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException)cause;
}
throw e;
}
final Map<String, T> result = new LinkedHashMap<>(map.size());
for (Map.Entry<String, JsonElement> e : map.entrySet()) {
result.put(e.getKey(), dtoProvider.fromJson(e.getValue()));
}
return new JsonStringMapImpl<>(result);
}
/**
* Parses the JSON data from the specified stream into map of objects of the specified type.
*
* @param json
* JSON data
* @param dtoInterface
* DTO interface
* @return map of DTO
* @throws IllegalArgumentException
* if can't provide any implementation for specified interface
* @throws IOException
* if an i/o error occurs
*/
public <T> JsonStringMap<T> createMapDtoFromJson(InputStream json, Class<T> dtoInterface) throws IOException {
return createMapDtoFromJson(new InputStreamReader(json), dtoInterface);
}
//
@SuppressWarnings("unchecked")
private <T> DtoProvider<T> getDtoProvider(Class<T> dtoInterface) {
DtoProvider<?> dtoProvider = dtoInterface2Providers.get(dtoInterface);
if (dtoProvider == null) {
throw new IllegalArgumentException("Unknown DTO type " + dtoInterface);
}
return (DtoProvider<T>)dtoProvider;
}
/**
* Registers DtoProvider for DTO interface.
*
* @param dtoInterface
* DTO interface
* @param provider
* provider for DTO interface
* @see DtoProvider
*/
public void registerProvider(Class<?> dtoInterface, DtoProvider<?> provider) {
dtoInterface2Providers.put(dtoInterface, provider);
dtoImpl2Providers.put(provider.getImplClass(), provider);
}
/**
* Unregisters DtoProvider.
*
* @see #registerProvider(Class, DtoProvider)
*/
public DtoProvider<?> unregisterProvider(Class<?> dtoInterface) {
final DtoProvider<?> dtoProvider = dtoInterface2Providers.remove(dtoInterface);
if (dtoProvider != null) {
dtoImpl2Providers.remove(dtoProvider.getImplClass());
}
return dtoProvider;
}
/** Test weather or not this DtoFactory has any DtoProvider which can provide implementation of DTO interface. */
public boolean hasProvider(Class<?> dtoInterface) {
return dtoInterface2Providers.get(dtoInterface) != null;
}
static {
for (DtoFactoryVisitor visitor : ServiceLoader.load(DtoFactoryVisitor.class)) {
visitor.accept(INSTANCE);
}
}
private DtoFactory() {
}
}