/* * Copyright (C) 2007 Clam <clamisgood@gmail.com> * Copyright (C) 2008 Quadduc <quadduc@gmail.com> * Copyright (C) 2010 IsmAvatar <IsmAvatar@gmail.com> * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.file; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.TreeSet; import org.lateralgm.file.ProjectFile.ResourceHolder; import org.lateralgm.main.UpdateSource; import org.lateralgm.main.UpdateSource.UpdateEvent; import org.lateralgm.main.UpdateSource.UpdateListener; import org.lateralgm.main.UpdateSource.UpdateTrigger; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Resource; import org.lateralgm.resources.ResourceReference; public class ResourceList<R extends InstantiableResource<R,?>> extends TreeSet<R> implements UpdateListener,ResourceHolder<R> { private static final long serialVersionUID = 1L; private static final IdComparator COMPARATOR = new IdComparator(); private final Class<R> type; // used as a workaround for add() private final HashMap<ResourceReference<R>,WeakReference<R>> refMap; private final UpdateTrigger updateTrigger = new UpdateTrigger(); public final UpdateSource updateSource = new UpdateSource(this,updateTrigger); ResourceList(Class<R> type) { super(COMPARATOR); this.type = type; refMap = new HashMap<ResourceReference<R>,WeakReference<R>>(); } public int lastId = -1; private boolean doAdd(R res) { WeakReference<R> wr = refMap.get(res.reference); R r0 = wr == null ? null : wr.get(); if (r0 != null) { if (r0 == res) return false; super.remove(r0); } res.reference.updateSource.addListener(this); refMap.put(res.reference,new WeakReference<R>(res)); return super.add(res); } /** * Notice: due to the nature of this list being sorted, * changing the id of a resource after adding it may cause * this list to become unsorted and result in unexpected behavior. */ public boolean add(R res) { //don't override id if it's already set if (res.getId() == -1) res.setId(++lastId); if (doAdd(res)) { updateTrigger.fire(); return true; } return false; } public boolean addAll(Collection<? extends R> c) { boolean r = false; for (R res : c) { res.setId(++lastId); r |= doAdd(res); } if (r) updateTrigger.fire(); return r; } /** * Notice: due to the nature of this list being sorted, * changing the id of a resource after adding it may cause * this list to become unsorted and result in unexpected behavior. */ public R add() { R res = null; try { res = type.newInstance(); } catch (Exception e) { e.printStackTrace(); } if (res != null) { res.setName(res.getName() + (lastId + 1)); add(res); } return res; } public R getResource() { return add(); } /** * Duplicates the given resource as per user request. Adds the duplicate to this list. */ @SuppressWarnings("unchecked") public R duplicate(Resource<?,?> src) { if (!this.contains(src)) return null; R dest = add(); ((R) src).copy(dest); return dest; } public R getUnsafe(int id) { for (R res : this) { int ri = res.getId(); if (ri == id) return res; else if (ri > id) break; } return null; } /** May return null */ public R get(String name) { for (R res : this) if (res.getName().equals(name)) return res; return null; } private boolean doRemove(Resource<?,?> res) { if (super.remove(res)) { res.reference.updateSource.removeListener(this); refMap.remove(res.reference); return true; } return false; } public boolean remove(Object o) { if (doRemove((Resource<?,?>) o)) { updateTrigger.fire(); return true; } return false; } public boolean removeAll(Collection<?> c) { boolean r = false; for (Object o : c) r |= doRemove((Resource<?,?>) o); if (r) updateTrigger.fire(); return r; } public boolean retainAll(Collection<?> c) { boolean r = false; for (R res : this) if (!c.contains(res)) r |= doRemove(res); if (r) updateTrigger.fire(); return r; } public void clear() { if (size() == 0) return; for (R r : this) r.reference.updateSource.removeListener(this); refMap.clear(); super.clear(); updateTrigger.fire(); } public void defragIds() { int i = 0; for (R res : this) res.setId(i++); lastId = i - 1; } public void updated(UpdateEvent e) { assert size() == refMap.size(); updateTrigger.fire(e); Object o = e.source.owner; if (o instanceof ResourceReference<?>) { ResourceReference<?> ref = (ResourceReference<?>) o; WeakReference<R> wr = refMap.get(ref); R r0 = wr == null ? null : wr.get(); if (r0 != null) { Resource<?,?> r = ref.get(); if (r0 != r) { remove(r0); if (r != null) add(type.cast(r)); } else { // Ensure that the set stays sorted. boolean changed = false; try { Comparator<? super R> c = comparator(); test: { R h = higher(r0); if (h != null && c.compare(r0,h) >= 0) { changed = true; break test; } R l = lower(r0); if (l != null && c.compare(r0,l) <= 0) { changed = true; break test; } } } catch (NoSuchMethodError nsme) { changed = true; } if (changed) { remove(r0); add(r0); } } } } } private static class IdComparator implements Comparator<InstantiableResource<?,?>> { public int compare(InstantiableResource<?,?> o1, InstantiableResource<?,?> o2) { if (o1.reference == o2.reference) return 0; int i1 = o1.getId(); int i2 = o2.getId(); if (i1 == i2) return Integer.signum(o1.hashCode() - o2.hashCode()); return i1 < i2 ? -1 : 1; } } }