/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jkiss.dbeaver.model.impl; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.meta.PropertyGroup; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.SQLDataSource; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.utils.BeanUtils; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * Various objects cache */ public abstract class AbstractObjectCache<OWNER extends DBSObject, OBJECT extends DBSObject> implements DBSObjectCache<OWNER, OBJECT> { private static final Log log = Log.getLog(AbstractObjectCache.class); private List<OBJECT> objectList; private Map<String, OBJECT> objectMap; protected volatile boolean fullCache = false; protected volatile boolean caseSensitive = true; protected Comparator<OBJECT> listOrderComparator; protected AbstractObjectCache() { } public void setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; } public Comparator<OBJECT> getListOrderComparator() { return listOrderComparator; } public void setListOrderComparator(Comparator<OBJECT> listOrderComparator) { this.listOrderComparator = listOrderComparator; } @NotNull @Override public List<OBJECT> getCachedObjects() { synchronized (this) { return objectList == null ? Collections.<OBJECT>emptyList() : objectList; } } public <SUB_TYPE> Collection<SUB_TYPE> getTypedObjects(DBRProgressMonitor monitor, OWNER owner, Class<SUB_TYPE> type) throws DBException { List<SUB_TYPE> result = new ArrayList<>(); for (OBJECT object : getAllObjects(monitor, owner)) { if (type.isInstance(object)) { result.add(type.cast(object)); } } return result; } @Nullable @Override public OBJECT getCachedObject(@NotNull String name) { synchronized (this) { return objectList == null || name == null ? null : getObjectMap().get(caseSensitive ? name : name.toUpperCase()); } } @Override public void cacheObject(@NotNull OBJECT object) { synchronized (this) { if (this.objectList == null) { this.objectList = new ArrayList<>(); } detectCaseSensitivity(object); this.objectList.add(object); if (this.objectMap != null) { String name = getObjectName(object); checkDuplicateName(name, object); this.objectMap.put(name, object); } } } @Override public void removeObject(@NotNull OBJECT object, boolean resetFullCache) { synchronized (this) { if (this.objectList != null) { detectCaseSensitivity(object); this.objectList.remove(object); if (this.objectMap != null) { this.objectMap.remove(getObjectName(object)); } } if (resetFullCache) { fullCache = false; } } } @Nullable public <SUB_TYPE> SUB_TYPE getObject(DBRProgressMonitor monitor, OWNER owner, String name, Class<SUB_TYPE> type) throws DBException { final OBJECT object = getObject(monitor, owner, name); return type.isInstance(object) ? type.cast(object) : null; } public boolean isFullyCached() { return this.fullCache; } @Override public void clearCache() { synchronized (this) { this.objectList = null; this.objectMap = null; this.fullCache = false; } } public void setCache(List<OBJECT> objects) { synchronized (this) { this.objectList = objects; this.objectMap = null; this.fullCache = true; } } /** * Merges new cache with existing. * If objects with the same name were already cached - leave them in cache * (because they might be referenced somewhere). */ protected void mergeCache(List<OBJECT> objects) { synchronized (this) { if (this.objectList != null) { // Merge lists objects = new ArrayList<>(objects); for (int i = 0; i < objects.size(); i++) { OBJECT newObject = objects.get(i); String newObjectName = getObjectName(newObject); for (int k = 0; k < objectList.size(); k++) { OBJECT oldObject = objectList.get(k); String oldObjectName = getObjectName(oldObject); if (newObjectName.equals(oldObjectName)) { objects.set(i, oldObject); break; } } } } } setCache(objects); } private synchronized Map<String, OBJECT> getObjectMap() { if (this.objectMap == null) { this.objectMap = new HashMap<>(); for (OBJECT object : objectList) { String name = getObjectName(object); checkDuplicateName(name, object); this.objectMap.put(name, object); } } return this.objectMap; } private void checkDuplicateName(String name, OBJECT object) { if (this.objectMap.containsKey(name)) { log.debug("Duplicate object name '" + name + "' in cache " + this.getClass().getSimpleName() + ". Last value: " + DBUtils.getObjectFullName(object, DBPEvaluationContext.DDL)); } } protected void detectCaseSensitivity(DBSObject object) { if (this.caseSensitive) { DBPDataSource dataSource = object.getDataSource(); if (dataSource instanceof SQLDataSource && ((SQLDataSource) dataSource).getSQLDialect().storesUnquotedCase() == DBPIdentifierCase.MIXED) { this.caseSensitive = false; } } } protected void invalidateObjects(DBRProgressMonitor monitor, OWNER owner, Iterator<OBJECT> objectIter) { } public void clearChildrenOf(DBSObject parent) { synchronized (this) { if (objectList == null) { return; } for (int i = 0; i < objectList.size(); ) { OBJECT object = objectList.get(i); if (object.getParentObject() == parent) { this.objectList.remove(object); if (this.objectMap != null) { this.objectMap.remove(getObjectName(object)); } fullCache = false; } else { i++; } } } } @NotNull protected String getObjectName(@NotNull OBJECT object) { String name; if (object instanceof DBPUniqueObject) { name = ((DBPUniqueObject) object).getUniqueName(); } else { name = object.getName(); } if (!caseSensitive) { return name.toUpperCase(); } return name; } /** * Performs a deep copy of srcObject into dstObject. * Copies all fields (recursively) and clears all nested caches */ protected void deepCopyCachedObject(@NotNull Object srcObject, @NotNull Object dstObject) { if (srcObject.getClass() != dstObject.getClass()) { log.error("Can't make object copy: src class " + srcObject.getClass().getName() + "' != dest class '" + dstObject.getClass().getName() + "'"); return; } try { for (Class<?> theClass = srcObject.getClass(); theClass != Object.class; theClass = theClass.getSuperclass()) { final Field[] fields = theClass.getDeclaredFields(); for (Field field : fields) { final int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers)) { continue; } field.setAccessible(true); final Object srcValue = field.get(srcObject); final Object dstValue = field.get(dstObject); if (DBSObjectCache.class.isAssignableFrom(field.getType())) { if (dstValue != null) { ((DBSObjectCache) dstValue).clearCache(); } } else { if (isPropertyGroupField(field)) { // This is a group of properties. Copy recursively // Just in case check that values not null and have the same type if (dstValue != null && srcValue != null && dstValue.getClass() == srcValue.getClass()) { deepCopyCachedObject(srcValue, dstValue); } } else if (Modifier.isFinal(modifiers)) { // Ignore final fields } else { // Just copy value field.set(dstObject, srcValue); } } } } } catch (Throwable e) { log.error("Error copying object state", e); } } protected class CacheIterator implements Iterator<OBJECT> { private Iterator<OBJECT> listIterator = objectList.iterator(); private OBJECT curObject; public CacheIterator() { } @Override public boolean hasNext() { return listIterator.hasNext(); } @Override public OBJECT next() { return (curObject = listIterator.next()); } @Override public void remove() { listIterator.remove(); if (objectMap != null) { objectMap.remove(getObjectName(curObject)); } } } public static boolean isPropertyGroupField(Field field) { String getterName = "get" + Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1); for (Method getter : field.getDeclaringClass().getMethods()) { if (getter.getName().equals(getterName) && isPropertyGetter(getter) && getter.getAnnotation(PropertyGroup.class) != null) { return true; } } return false; } public static boolean isPropertyGetter(Method method) { if (BeanUtils.isGetterName(method.getName())) { return method.getParameterTypes().length == 0 || (method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == DBRProgressMonitor.class); } return false; } }