Java tutorial
/* * Copyright (C) 2016 Edmund Klaus * * 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.objectpocket; import java.io.IOException; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; import javax.swing.SwingWorker; import org.objectpocket.exception.ObjectPocketException; import org.objectpocket.gson.CustomTypeAdapterFactory; import org.objectpocket.references.ReferenceSupport; import org.objectpocket.storage.ObjectStore; import org.objectpocket.storage.blob.BlobStore; import org.objectpocket.util.IdSupport; import org.objectpocket.util.JsonHelper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * * @author Edmund Klaus * */ public class ObjectPocketImpl implements ObjectPocket { private ObjectStore objectStore; private BlobStore blobStore; private boolean serializeNulls = false; private boolean prettyPrinting = false; private boolean writeBackup = true; private boolean objectStoreInitialized = false; private boolean dirty = false; private Map<Type, Set<Object>> typeAdapterMap = new HashMap<Type, Set<Object>>(10); private Set<ReferenceSupport> referenceSupportSet = new HashSet<ReferenceSupport>(10); private Gson gson = null; private boolean loading = false; // <typeName:<id,object>> private Map<String, Map<String, Object>> objectMap = new HashMap<String, Map<String, Object>>(1000000); // this extra map<object,id> is necessary for faster lookup of already // traced objects // objectMap.values.values is too slow for a proper lookup private Map<Object, String> tracedObjects = new ConcurrentHashMap<Object, String>(1000000); private Set<Object> serializeAsRoot; // holds specific filenames for objects, set by the user private Map<Object, String> objectFilenames = new HashMap<Object, String>(1000000); // <object, id> private Map<Object, String> idsFromReadObjects = new HashMap<Object, String>(1000000); @SuppressWarnings("unused") private ObjectPocketImpl() { } protected ObjectPocketImpl(ObjectStore objectStore) { this.objectStore = objectStore; } @Override public void add(Object obj) throws ObjectPocketException { if (obj == null) { return; } if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } // TODO: check who owns the object (in case more than 1 ObjectPocket) // if (obj.getOwningInstance() == null || // obj.getOwningInstance().equals(this)) { // // attach given object to this japer instance // obj.setOwningInstance(this); // // add object to objectMap String typeName = obj.getClass().getTypeName(); if (objectMap.get(typeName) == null) { objectMap.put(typeName, new HashMap<String, Object>()); } Map<String, Object> map = objectMap.get(typeName); if (!tracedObjects.containsKey(obj)) { String objectId = IdSupport.getId(obj, false); tracedObjects.put(obj, objectId); map.put(objectId, obj); dirty = true; // this is necessary when copying blob data from // one ObjectPocket to another if (obj instanceof Blob) { ((Blob) obj).prepareToPersist(); // set blobStore to ensure that the correct blob store is set // do not do this before prepareToPersist, it would cause // problems // when copying blobs from one ObjectPocket to another! ((Blob) obj).setBlobStore(blobStore); } } // add references addReferences(obj); // } else { // obj.getOwningInstance().add(obj); // } } @Override public void add(Object obj, String filename) throws ObjectPocketException { // TODO: validate filename to not be something like /home... or C:/... // throw exception in that case? if (obj == null) { return; } this.add(obj); if (tracedObjects.containsKey(obj) && filename != null && !filename.trim().isEmpty()) { objectFilenames.put(obj, filename); } } private void addReferences(Object obj) throws ObjectPocketException { for (ReferenceSupport referenceSupport : referenceSupportSet) { Set<Object> references = referenceSupport.getReferences(obj); if (references != null) { for (Object reference : references) { // this supports cyclic references between objects if (reference != null) { if (!tracedObjects.containsKey(reference)) { // System.out.println(reference + // " is not traced, but referenced by " + obj); add(reference); } } } } } } @Override public void store() throws ObjectPocketException { if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } long time = System.currentTimeMillis(); serializeAsRoot = new HashSet<Object>(); // rescan for references for (Object obj : tracedObjects.keySet()) { addReferences(obj); } // update ids for all objects, // they might have been changed by the user in the meantime objectMap.clear(); String newId = null; for (Object obj : tracedObjects.keySet()) { newId = IdSupport.getId(obj, false, tracedObjects.get(obj)); tracedObjects.put(obj, newId); String typeName = obj.getClass().getName(); Map<String, Object> map = objectMap.get(typeName); if (map == null) { map = new HashMap<String, Object>(); objectMap.put(typeName, map); } map.put(newId, obj); } // go through all types that have been add to ObjectPocket and collect // data to persist Map<String, Map<String, Set<String>>> jsonObjects = new HashMap<String, Map<String, Set<String>>>(); Set<Blob> blobsToPersist = new HashSet<Blob>(); Gson gson = configureGson(); for (String typeName : objectMap.keySet()) { // collect objects Map<String, Object> map = objectMap.get(typeName); if (map.values() == null) { return; } Map<String, Set<String>> jsonStrings = new HashMap<String, Set<String>>(map.values().size()); String jsonString = null; String filename = typeName; for (String id : map.keySet()) { // TODO: Is this necessary any more? // if (!identifiable.isProxy()) { // identifiable.serializeAsRoot = true; Object object = map.get(id); if (object instanceof ProxyOut) { System.out.println("proxyOut"); } serializeAsRoot.add(object); StringBuilder sb = new StringBuilder(gson.toJson(object)); jsonString = JsonHelper.addTypeAndIdToJson(sb, typeName, IdSupport.getId(object, true, id), prettyPrinting); if (objectFilenames.get(object) != null) { filename = objectFilenames.get(object); } if (jsonStrings.get(filename) == null) { jsonStrings.put(filename, new HashSet<String>()); } jsonStrings.get(filename).add(jsonString); // } } jsonObjects.put(typeName, jsonStrings); // collect blob data try { Class<?> clazz = Class.forName(typeName); if (Blob.class.isAssignableFrom(clazz)) { for (Object o : objectMap.get(typeName).values()) { Blob blob = (Blob) o; if (blob.doPersist()) { blobsToPersist.add(blob); } } } } catch (ClassNotFoundException e) { throw new ObjectPocketException("Could not collect blobs for typeName. " + typeName, e); } } // write backup if (writeBackup) { try { objectStore.createBackup(); } catch (IOException e) { throw new ObjectPocketException("Could not backup current data", e); } } // persist object data try { objectStore.writeJsonObjects(jsonObjects); } catch (IOException e) { throw new ObjectPocketException("Could not persist objects.", e); } // persist blob data try { blobStore.writeBlobs(blobsToPersist); } catch (IOException e) { throw new ObjectPocketException("Could not persist blobs.", e); } objectStoreInitialized = true; dirty = false; Logger.getAnonymousLogger().info("Stored all objects in " + objectStore.getSource() + " in " + (System.currentTimeMillis() - time) + " ms."); } @Override public void load() throws ObjectPocketException { if (!objectStore.exists()) { throw new ObjectPocketException( "The given object pocket store does not exist. " + objectStore.getSource()); } loading = true; long timeAll = System.currentTimeMillis(); idsFromReadObjects.clear(); tracedObjects.clear(); objectMap.clear(); /** * get all available object types */ Set<String> availableObjectTypes = null; try { availableObjectTypes = objectStore.getAvailableObjectTypes(); } catch (IOException e) { loading = false; throw new ObjectPocketException("Could not acquire available objects.", e); } // ExecutorService threadPool = Executors.newCachedThreadPool(); /** * load json objects strings into real objects */ if (availableObjectTypes != null) { for (String typeName : availableObjectTypes) { // Runnable r = new Runnable() { // @Override // public void run() { // try { // loadObjectsFromJsonStrings(typeName); // } catch (ClassNotFoundException | IOException e) { // //throw new // ObjectPocketException("Could not load objects for type. " + // typeName, e); // Logger.getAnonymousLogger().log(Level.SEVERE, // "Could not load objects for type. " + typeName, e); // } // } // }; // // threadPool.execute(r); try { loadObjectsFromJsonStrings(typeName); } catch (ClassNotFoundException | IOException e) { loading = false; throw new ObjectPocketException("Could not load objects for type. " + typeName, e); } } } // threadPool.shutdown(); // try { // threadPool.awaitTermination(60, TimeUnit.SECONDS); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } injectReferences(); Logger.getAnonymousLogger().info("Loaded all objects from " + objectStore.getSource() + " in " + (System.currentTimeMillis() - timeAll) + " ms."); loading = false; objectStoreInitialized = true; dirty = false; } @Override public void loadAsynchronous(Class<?>... preload) throws ObjectPocketException { if (!objectStore.exists()) { throw new ObjectPocketException( "The given object pocket store does not exist. " + objectStore.getSource()); } loading = true; long timeAll = System.currentTimeMillis(); /** * get all available object types */ Set<String> availableObjectTypes = null; try { availableObjectTypes = new HashSet<String>(objectStore.getAvailableObjectTypes()); } catch (IOException e) { loading = false; throw new ObjectPocketException("Could not acquire available objects.", e); } if (preload != null) { for (Class<?> type : preload) { if (type != null) { // remove type from all types availableObjectTypes.remove(type.getName()); // preload objects for this type try { loadObjectsFromJsonStrings(type.getName()); } catch (ClassNotFoundException | IOException e) { loading = false; throw new ObjectPocketException("Could not load objects for type. " + type.getName(), e); } } } } injectReferences(); objectStoreInitialized = true; final Set<String> otherTypes = availableObjectTypes; // do everything else asynchronous SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { // load the rest of object types for (String typeName : otherTypes) { try { loadObjectsFromJsonStrings(typeName); } catch (ClassNotFoundException | IOException e) { loading = false; throw new ObjectPocketException( "Could not load objects asynchronously for type. " + typeName, e); } } injectReferences(); Logger.getAnonymousLogger().info("Loaded all objects from " + objectStore.getSource() + " in " + (System.currentTimeMillis() - timeAll) + " ms."); loading = false; dirty = false; return null; } @Override protected void done() { try { loading = false; get(); } catch (InterruptedException | ExecutionException e) { loading = false; Logger.getAnonymousLogger().severe("Could not load objects asynchronously."); e.printStackTrace(); } } }; sw.execute(); } @Override public boolean isLoading() { return loading; } @Override @SuppressWarnings("unchecked") public <T> T find(String id, Class<T> type) throws ObjectPocketException { if (id == null || id.isEmpty()) { return null; } if (type == null) { return null; } if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } Map<String, Object> map = objectMap.get(type.getName()); if (map != null) { return (T) map.get(id); } return null; } @Override @SuppressWarnings("unchecked") public <T> Collection<T> findAll(Class<T> type) throws ObjectPocketException { if (type != null) { if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } Map<String, Object> map = objectMap.get(type.getName()); if (map != null && !map.isEmpty()) { return new HashSet<>((Collection<T>) map.values()); } } return null; } @Override public void remove(Object obj) throws ObjectPocketException { if (obj == null) { return; } String id = tracedObjects.get(obj); if (id == null) { return; } if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } Map<String, Object> map = objectMap.get(obj.getClass().getName()); if (map == null) { return; } map.remove(id); tracedObjects.remove(obj); dirty = true; // remove referenced Blob objects if (!(obj instanceof Blob)) { for (ReferenceSupport referenceSupport : referenceSupportSet) { Set<Object> references = referenceSupport.getReferences(obj); if (references != null) { for (Object reference : references) { if (reference != null && reference instanceof Blob) { remove(reference); } } } } } } @Override public void cleanup() throws ObjectPocketException { Logger.getAnonymousLogger().info("Start performing cleanup for " + objectStore.getSource()); long time = System.currentTimeMillis(); if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } if (dirty) { throw new ObjectPocketException("The state of ObjectPocket is dirty. Please call store() or load()."); } Map<String, Object> blobMap = objectMap.get(Blob.class.getName()); if (blobMap != null) { Collection<Object> values = blobMap.values(); Set<Blob> blobSet = new HashSet<Blob>(values.size()); for (Object object : values) { blobSet.add((Blob) object); } try { blobStore.cleanup(blobSet); } catch (IOException e) { throw new ObjectPocketException("Could not perform cleanup.", e); } } else { try { blobStore.delete(); } catch (IOException e) { throw new ObjectPocketException("Could not delete blob store.", e); } } Logger.getAnonymousLogger().info("Successfully performed cleanup for " + objectStore.getSource() + " in " + (System.currentTimeMillis() - time) + " ms."); } @Override public void close() throws IOException { if (objectStore != null) { objectStore.close(); } if (blobStore != null) { blobStore.close(); } } @Override public boolean exists() { return objectStore.exists(); } @Override public void link(ObjectPocket objectPocket) { throw new UnsupportedOperationException(); } @Override public void setDefaultFilename(Class<?> type, String filename) throws ObjectPocketException { if (!storeIsReady()) { throw new ObjectPocketException("The desired location contains data. Please load the data first."); } dirty = true; throw new UnsupportedOperationException(); } private void loadObjectsFromJsonStrings(String typeName) throws ClassNotFoundException, IOException { Class<?> clazz = Class.forName(typeName); boolean setBlobStore = false; if (Blob.class.isAssignableFrom(clazz)) { setBlobStore = true; } long time = System.currentTimeMillis(); int counter = 0; Map<String, Map<String, String>> fileToJsonObjectsMapping = objectStore.readJsonObjects(typeName); if (fileToJsonObjectsMapping != null && !fileToJsonObjectsMapping.isEmpty()) { HashMap<String, Object> objectAndIdMap = new HashMap<String, Object>(); for (String filename : fileToJsonObjectsMapping.keySet()) { Map<String, String> jsonObjects = fileToJsonObjectsMapping.get(filename); Gson gson = configureGson(); // remove json file extension filename = filename.substring(0, filename.length() - 5); for (String jsonObject : jsonObjects.keySet()) { Object object = gson.fromJson(jsonObject, clazz); // TODO: map to owning ObjectPocket // object.setOwningInstance(this); if (setBlobStore) { ((Blob) object).setBlobStore(blobStore); } String id = IdSupport.getId(object, jsonObjects.get(jsonObject)); tracedObjects.put(object, id); objectAndIdMap.put(id, object); if (!object.getClass().getName().equals(filename)) { objectFilenames.put(object, filename); } counter++; } } objectMap.put(typeName, objectAndIdMap); } Logger.getAnonymousLogger().info("Loaded " + counter + " objects of type\n " + clazz.getName() + " in " + (System.currentTimeMillis() - time) + " ms"); } private void injectReferences() { long time = System.currentTimeMillis(); for (ReferenceSupport referenceSupport : referenceSupportSet) { Map<String, Map<String, Object>> globalMap = new HashMap<String, Map<String, Object>>(objectMap); // TODO: extends to more instances // for (JaperImpl japer : otherJapers) { // amendMap(globalMap, japer.objectMap); // } for (String typeName : globalMap.keySet()) { Collection<Object> values = globalMap.get(typeName).values(); for (Object object : values) { referenceSupport.injectReferences(object, globalMap, idsFromReadObjects); } } } Logger.getAnonymousLogger().info("Injection took " + (System.currentTimeMillis() - time) + " ms"); } // private void amendMap(Map<String, Map<String, Object>> dest, // Map<String, Map<String, Object>> source) { // for (String key : source.keySet()) { // if (dest.get(key) != null) { // Map<String, Object> map = dest.get(key); // map.putAll(source.get(key)); // } else { // dest.put(key, source.get(key)); // } // } // } public void addIdFromReadObject(Object object, String id) { idsFromReadObjects.put(object, id); } private Gson configureGson() { if (gson == null) { GsonBuilder gsonBuilder = new GsonBuilder(); // null serialization if (serializeNulls) { gsonBuilder.serializeNulls(); } // This is where the referencing entry magic happens gsonBuilder.registerTypeAdapterFactory(new CustomTypeAdapterFactory(this)); // add custom type adapters for (Type type : typeAdapterMap.keySet()) { for (Object typeAdapter : typeAdapterMap.get(type)) { gsonBuilder.registerTypeAdapter(type, typeAdapter); } } // pretty printing if (prettyPrinting) { gsonBuilder.setPrettyPrinting(); } gson = gsonBuilder.create(); } return gson; } public void serializeNulls() { serializeNulls = true; } public void setPrettyPrinting() { prettyPrinting = true; } public void doNotWriteBackups() { writeBackup = false; } public void setTypeAdapterMap(Map<Type, Set<Object>> typeAdapterMap) { this.typeAdapterMap = typeAdapterMap; } public void setBlobStore(BlobStore blobStore) { this.blobStore = blobStore; } public void addReferenceSupport(ReferenceSupport referenceSupport) { referenceSupportSet.add(referenceSupport); } public String getIdForObject(Object obj) { return tracedObjects.get(obj); } public boolean isSerializeAsRoot(Object obj) { return serializeAsRoot.contains(obj); } public void setSerializeAsRoot(Object obj, boolean val) { if (val) { serializeAsRoot.add(obj); } else { serializeAsRoot.remove(obj); } } private boolean storeIsReady() { return objectStoreInitialized || !objectStore.exists(); } /** * Returns the source where the data is loaded from. * * @return */ public String getSource() { return objectStore.getSource(); } public Set<String> getAvailableTypes() { return objectMap.keySet(); } public Map<String, Object> getMapForType(String typeName) { return objectMap.get(typeName); } }